diff options
235 files changed, 4954 insertions, 2764 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 48d6392f8b9b..8591a9c4a195 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -77,6 +77,7 @@ aconfig_declarations_group { "camera_platform_flags_core_java_lib", "com.android.hardware.input-aconfig-java", "com.android.input.flags-aconfig-java", + "com.android.internal.compat.flags-aconfig-java", "com.android.internal.foldables.flags-aconfig-java", "com.android.internal.pm.pkg.component.flags-aconfig-java", "com.android.media.flags.bettertogether-aconfig-java", @@ -663,6 +664,13 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// Platform Compat +java_aconfig_library { + name: "com.android.internal.compat.flags-aconfig-java", + aconfig_declarations: "compat_logging_flags", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Multi user aconfig_declarations { name: "android.multiuser.flags-aconfig", diff --git a/apex/jobscheduler/service/Android.bp b/apex/jobscheduler/service/Android.bp index 0104ee14fec4..ace56d42ddd1 100644 --- a/apex/jobscheduler/service/Android.bp +++ b/apex/jobscheduler/service/Android.bp @@ -20,6 +20,7 @@ java_library { ], libs: [ + "androidx.annotation_annotation", "app-compat-annotations", "error_prone_annotations", "framework", diff --git a/core/api/current.txt b/core/api/current.txt index c810691fcb4d..e0b919a8dc4a 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8084,7 +8084,7 @@ package android.app.admin { method public CharSequence getStartUserSessionMessage(@NonNull android.content.ComponentName); method @Deprecated public boolean getStorageEncryption(@Nullable android.content.ComponentName); method public int getStorageEncryptionStatus(); - method @FlaggedApi("android.app.admin.flags.esim_management_enabled") @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS) public java.util.Set<java.lang.Integer> getSubscriptionsIds(); + method @FlaggedApi("android.app.admin.flags.esim_management_enabled") @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS) public java.util.Set<java.lang.Integer> getSubscriptionIds(); method @Nullable public android.app.admin.SystemUpdatePolicy getSystemUpdatePolicy(); method @Nullable public android.os.PersistableBundle getTransferOwnershipBundle(); method @Nullable public java.util.List<android.os.PersistableBundle> getTrustAgentConfiguration(@Nullable android.content.ComponentName, @NonNull android.content.ComponentName); @@ -9602,8 +9602,8 @@ package android.appwidget { method public static android.appwidget.AppWidgetManager getInstance(android.content.Context); method @FlaggedApi("android.appwidget.flags.generated_previews") @Nullable public android.widget.RemoteViews getWidgetPreview(@NonNull android.content.ComponentName, @Nullable android.os.UserHandle, int); method public boolean isRequestPinAppWidgetSupported(); - method public void notifyAppWidgetViewDataChanged(int[], int); - method public void notifyAppWidgetViewDataChanged(int, int); + method @Deprecated public void notifyAppWidgetViewDataChanged(int[], int); + method @Deprecated public void notifyAppWidgetViewDataChanged(int, int); method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews); method public void partiallyUpdateAppWidget(int, android.widget.RemoteViews); method @FlaggedApi("android.appwidget.flags.generated_previews") public void removeWidgetPreview(@NonNull android.content.ComponentName, int); @@ -28054,7 +28054,7 @@ package android.media.tv.ad { method public void sendSigningResult(@NonNull String, @NonNull byte[]); method public void sendTrackInfoList(@Nullable java.util.List<android.media.tv.TvTrackInfo>); method public void setCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdView.TvAdCallback); - method public void setOnUnhandledInputEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdView.OnUnhandledInputEventListener); + method public void setOnUnhandledInputEventListener(@NonNull android.media.tv.ad.TvAdView.OnUnhandledInputEventListener); method public boolean setTvView(@Nullable android.media.tv.TvView); method public void startAdService(); method public void stopAdService(); @@ -52447,8 +52447,8 @@ package android.view { method public final void cancelPendingInputEvents(); method public boolean checkInputConnectionProxy(android.view.View); method public void clearAnimation(); - method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void clearCredentialManagerRequest(); method public void clearFocus(); + method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void clearPendingCredentialRequest(); method public void clearViewTranslationCallback(); method public static int combineMeasuredStates(int, int); method protected int computeHorizontalScrollExtent(); @@ -52557,8 +52557,6 @@ package android.view { method @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") public final int getContentSensitivity(); method @UiContext public final android.content.Context getContext(); method protected android.view.ContextMenu.ContextMenuInfo getContextMenuInfo(); - method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public final android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException> getCredentialManagerCallback(); - method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public final android.credentials.GetCredentialRequest getCredentialManagerRequest(); method public final boolean getDefaultFocusHighlightEnabled(); method public static int getDefaultSize(int, int); method public android.view.Display getDisplay(); @@ -52643,6 +52641,8 @@ package android.view { method public int getPaddingTop(); method public final android.view.ViewParent getParent(); method public android.view.ViewParent getParentForAccessibility(); + method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public final android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException> getPendingCredentialCallback(); + method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public final android.credentials.GetCredentialRequest getPendingCredentialRequest(); method public float getPivotX(); method public float getPivotY(); method public android.view.PointerIcon getPointerIcon(); @@ -52943,7 +52943,6 @@ package android.view { method public void setContentDescription(CharSequence); method @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") public final void setContentSensitivity(int); method public void setContextClickable(boolean); - method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void setCredentialManagerRequest(@NonNull android.credentials.GetCredentialRequest, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>); method public void setDefaultFocusHighlightEnabled(boolean); method @Deprecated public void setDrawingCacheBackgroundColor(@ColorInt int); method @Deprecated public void setDrawingCacheEnabled(boolean); @@ -53022,6 +53021,7 @@ package android.view { method public void setOverScrollMode(int); method public void setPadding(int, int, int, int); method public void setPaddingRelative(int, int, int, int); + method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void setPendingCredentialRequest(@NonNull android.credentials.GetCredentialRequest, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>); method public void setPivotX(float); method public void setPivotY(float); method public void setPointerIcon(android.view.PointerIcon); @@ -53825,10 +53825,10 @@ package android.view { method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void clearCredentialManagerRequest(); method @Nullable public abstract android.view.autofill.AutofillId getAutofillId(); method public abstract int getChildCount(); - method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException> getCredentialManagerCallback(); - method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public android.credentials.GetCredentialRequest getCredentialManagerRequest(); method public abstract android.os.Bundle getExtras(); method public abstract CharSequence getHint(); + method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException> getPendingCredentialCallback(); + method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public android.credentials.GetCredentialRequest getPendingCredentialRequest(); method public abstract CharSequence getText(); method public abstract int getTextSelectionEnd(); method public abstract int getTextSelectionStart(); @@ -53851,7 +53851,6 @@ package android.view { method public abstract void setClickable(boolean); method public abstract void setContentDescription(CharSequence); method public abstract void setContextClickable(boolean); - method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void setCredentialManagerRequest(@NonNull android.credentials.GetCredentialRequest, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>); method public abstract void setDataIsSensitive(boolean); method public abstract void setDimens(int, int, int, int, int, int); method public abstract void setElevation(float); @@ -53870,6 +53869,7 @@ package android.view { method public void setMaxTextLength(int); method public void setMinTextEms(int); method public abstract void setOpaque(boolean); + method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void setPendingCredentialRequest(@NonNull android.credentials.GetCredentialRequest, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>); method public void setReceiveContentMimeTypes(@Nullable String[]); method public abstract void setSelected(boolean); method public abstract void setText(CharSequence); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index b73f1993ee4c..5645119b2f6d 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1306,6 +1306,7 @@ package android.app.admin { public class DevicePolicyManager { method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int checkProvisioningPrecondition(@NonNull String, @NonNull String); + method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void clearAuditLogEventCallback(); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException; method @Nullable public android.content.Intent createProvisioningIntentFromNfcIntent(@NonNull android.content.Intent); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void finalizeWorkProfileProvisioning(@NonNull android.os.UserHandle, @Nullable android.accounts.Account); @@ -1342,7 +1343,7 @@ package android.app.admin { method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException; method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public void setApplicationExemptions(@NonNull String, @NonNull java.util.Set<java.lang.Integer>) throws android.content.pm.PackageManager.NameNotFoundException; method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEnabled(boolean); - method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback(@NonNull java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.util.List<android.app.admin.SecurityLog.SecurityEvent>>); + method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.admin.SecurityLog.SecurityEvent>>); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied(); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean); method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setMaxPolicyStorageLimit(int); @@ -6260,7 +6261,7 @@ package android.hardware.radio { method public void addOnCompleteListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.OnCompleteListener); method public void addOnCompleteListener(@NonNull android.hardware.radio.ProgramList.OnCompleteListener); method public void close(); - method @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier); + method @Deprecated @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier); method @FlaggedApi("android.hardware.radio.hd_radio_improved") @NonNull public java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramInfos(@NonNull android.hardware.radio.ProgramSelector.Identifier); method public void registerListCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.ListCallback); method public void registerListCallback(@NonNull android.hardware.radio.ProgramList.ListCallback); @@ -6312,7 +6313,7 @@ package android.hardware.radio { field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; // 0x5 field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SID_EXT = 5; // 0x5 field public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; // 0xa - field public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb + field @Deprecated public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9 field public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3; // 0x3 field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int IDENTIFIER_TYPE_HD_STATION_LOCATION = 15; // 0xf @@ -6320,8 +6321,8 @@ package android.hardware.radio { field @Deprecated public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4 field public static final int IDENTIFIER_TYPE_INVALID = 0; // 0x0 field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2 - field public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd - field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc + field @Deprecated public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd + field @Deprecated public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc field public static final int IDENTIFIER_TYPE_VENDOR_END = 1999; // 0x7cf field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = 1999; // 0x7cf field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = 1000; // 0x3e8 @@ -6374,7 +6375,7 @@ package android.hardware.radio { field public static final int CONFIG_DAB_DAB_SOFT_LINKING = 8; // 0x8 field public static final int CONFIG_DAB_FM_LINKING = 7; // 0x7 field public static final int CONFIG_DAB_FM_SOFT_LINKING = 9; // 0x9 - field public static final int CONFIG_FORCE_ANALOG = 2; // 0x2 + field @Deprecated public static final int CONFIG_FORCE_ANALOG = 2; // 0x2 field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_AM = 11; // 0xb field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_FM = 10; // 0xa field public static final int CONFIG_FORCE_DIGITAL = 3; // 0x3 @@ -14213,7 +14214,6 @@ package android.telecom { method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsSupportingScheme(String); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isInEmergencyCall(); method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isInSelfManagedCall(@NonNull String, @NonNull android.os.UserHandle); - method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isInSelfManagedCall(@NonNull String, boolean); method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUserSelectedOutgoingPhoneAccount(@Nullable android.telecom.PhoneAccountHandle); field public static final String ACTION_CURRENT_TTY_MODE_CHANGED = "android.telecom.action.CURRENT_TTY_MODE_CHANGED"; @@ -15393,7 +15393,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean matchesCurrentSimOperator(@NonNull String, int, @Nullable String); method public boolean needsOtaServiceProvisioning(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled(); - method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.DUMP) public void persistEmergencyCallDiagnosticData(@NonNull String, @NonNull android.telephony.TelephonyManager.EmergencyCallDiagnosticData); + method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.READ_DROPBOX_DATA) public void persistEmergencyCallDiagnosticData(@NonNull String, @NonNull android.telephony.TelephonyManager.EmergencyCallDiagnosticData); method @RequiresPermission(android.Manifest.permission.REBOOT) public int prepareForUnattendedReboot(); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerCarrierPrivilegesCallback(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CarrierPrivilegesCallback); diff --git a/core/java/Android.bp b/core/java/Android.bp index ab1c9a4ef48d..4f96206bfd08 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -167,6 +167,9 @@ java_library { "com/android/internal/logging/UiEventLoggerImpl.java", ":statslog-framework-java-gen", ], + libs: [ + "androidx.annotation_annotation", + ], static_libs: ["modules-utils-uieventlogger-interface"], } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 1d39186de183..ae5cacd18aa2 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -974,6 +974,7 @@ public final class ActivityThread extends ClientTransactionHandler ContentCaptureOptions contentCaptureOptions; long[] disabledCompatChanges; + long[] mLoggableCompatChanges; SharedMemory mSerializedSystemFontMap; @@ -1283,6 +1284,7 @@ public final class ActivityThread extends ClientTransactionHandler AutofillOptions autofillOptions, ContentCaptureOptions contentCaptureOptions, long[] disabledCompatChanges, + long[] loggableCompatChanges, SharedMemory serializedSystemFontMap, long startRequestedElapsedTime, long startRequestedUptime) { @@ -1337,6 +1339,7 @@ public final class ActivityThread extends ClientTransactionHandler data.autofillOptions = autofillOptions; data.contentCaptureOptions = contentCaptureOptions; data.disabledCompatChanges = disabledCompatChanges; + data.mLoggableCompatChanges = loggableCompatChanges; data.mSerializedSystemFontMap = serializedSystemFontMap; data.startRequestedElapsedTime = startRequestedElapsedTime; data.startRequestedUptime = startRequestedUptime; @@ -4073,6 +4076,13 @@ public final class ActivityThread extends ClientTransactionHandler ActivityManager.getService().waitForNetworkStateUpdate(mNetworkBlockSeq); mNetworkBlockSeq = INVALID_PROC_STATE_SEQ; } catch (RemoteException ignored) {} + if (Flags.clearDnsCacheOnNetworkRulesUpdate()) { + // InetAddress will cache UnknownHostException failures. If the rules got + // updated and the app has network access now, we need to clear the negative + // cache to ensure valid dns queries can work immediately. + // TODO: b/329133769 - Clear only the negative cache once it is available. + InetAddress.clearDnsCache(); + } } } } @@ -7123,7 +7133,7 @@ public final class ActivityThread extends ClientTransactionHandler Process.setStartTimes(SystemClock.elapsedRealtime(), SystemClock.uptimeMillis(), data.startRequestedElapsedTime, data.startRequestedUptime); - AppCompatCallbacks.install(data.disabledCompatChanges); + AppCompatCallbacks.install(data.disabledCompatChanges, data.mLoggableCompatChanges); // Let libcore handle any compat changes after installing the list of compat changes. AppSpecializationHooks.handleCompatChangesBeforeBindingApplication(); diff --git a/core/java/android/app/AppCompatCallbacks.java b/core/java/android/app/AppCompatCallbacks.java index 134cef5b6bfa..f2debfcfa6b1 100644 --- a/core/java/android/app/AppCompatCallbacks.java +++ b/core/java/android/app/AppCompatCallbacks.java @@ -30,41 +30,59 @@ import java.util.Arrays; */ public final class AppCompatCallbacks implements Compatibility.BehaviorChangeDelegate { private final long[] mDisabledChanges; + private final long[] mLoggableChanges; private final ChangeReporter mChangeReporter; /** - * Install this class into the current process. + * Install this class into the current process using the disabled and loggable changes lists. * * @param disabledChanges Set of compatibility changes that are disabled for this process. + * @param loggableChanges Set of compatibility changes that we want to log. */ - public static void install(long[] disabledChanges) { - Compatibility.setBehaviorChangeDelegate(new AppCompatCallbacks(disabledChanges)); + public static void install(long[] disabledChanges, long[] loggableChanges) { + Compatibility.setBehaviorChangeDelegate( + new AppCompatCallbacks(disabledChanges, loggableChanges)); } - private AppCompatCallbacks(long[] disabledChanges) { + private AppCompatCallbacks(long[] disabledChanges, long[] loggableChanges) { mDisabledChanges = Arrays.copyOf(disabledChanges, disabledChanges.length); + mLoggableChanges = Arrays.copyOf(loggableChanges, loggableChanges.length); Arrays.sort(mDisabledChanges); - mChangeReporter = new ChangeReporter( - ChangeReporter.SOURCE_APP_PROCESS); + Arrays.sort(mLoggableChanges); + mChangeReporter = new ChangeReporter(ChangeReporter.SOURCE_APP_PROCESS); + } + + /** + * Helper to determine if a list contains a changeId. + * + * @param list to search through + * @param changeId for which to search in the list + * @return true if the given changeId is found in the provided array. + */ + private boolean changeIdInChangeList(long[] list, long changeId) { + return Arrays.binarySearch(list, changeId) >= 0; } public void onChangeReported(long changeId) { - reportChange(changeId, ChangeReporter.STATE_LOGGED); + boolean isLoggable = changeIdInChangeList(mLoggableChanges, changeId); + reportChange(changeId, ChangeReporter.STATE_LOGGED, isLoggable); } public boolean isChangeEnabled(long changeId) { - if (Arrays.binarySearch(mDisabledChanges, changeId) < 0) { - // Not present in the disabled array - reportChange(changeId, ChangeReporter.STATE_ENABLED); + boolean isEnabled = !changeIdInChangeList(mDisabledChanges, changeId); + boolean isLoggable = changeIdInChangeList(mLoggableChanges, changeId); + if (isEnabled) { + // Not present in the disabled changeId array + reportChange(changeId, ChangeReporter.STATE_ENABLED, isLoggable); return true; } - reportChange(changeId, ChangeReporter.STATE_DISABLED); + reportChange(changeId, ChangeReporter.STATE_DISABLED, isLoggable); return false; } - private void reportChange(long changeId, int state) { + private void reportChange(long changeId, int state, boolean isLoggable) { int uid = Process.myUid(); - mChangeReporter.reportChange(uid, changeId, state); + mChangeReporter.reportChange(uid, changeId, state, isLoggable); } } diff --git a/core/java/android/app/DreamManager.java b/core/java/android/app/DreamManager.java index 7c8b0fd499e3..ef6982e4a13c 100644 --- a/core/java/android/app/DreamManager.java +++ b/core/java/android/app/DreamManager.java @@ -185,6 +185,22 @@ public class DreamManager { } /** + * Whether dreaming can start given user settings and the current dock/charge state. + * + * @hide + */ + @UserHandleAware + @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) + public boolean canStartDreaming(boolean isScreenOn) { + try { + return mService.canStartDreaming(isScreenOn); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return false; + } + + /** * Returns whether the device is Dreaming. * * <p> This is only used for testing the dream service APIs. diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl index a04620cafd75..251e4e8ad834 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -90,7 +90,7 @@ oneway interface IApplicationThread { in CompatibilityInfo compatInfo, in Map services, in Bundle coreSettings, in String buildSerial, in AutofillOptions autofillOptions, in ContentCaptureOptions contentCaptureOptions, in long[] disabledCompatChanges, - in SharedMemory serializedSystemFontMap, + in long[] loggableCompatChanges, in SharedMemory serializedSystemFontMap, long startRequestedElapsedTime, long startRequestedUptime); void runIsolatedEntryPoint(in String entryPoint, in String[] entryPointArgs); void scheduleExit(); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index b25ebf69d14c..620bbaf4bbf5 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -14090,9 +14090,7 @@ public class DevicePolicyManager { try { return mService.isAuditLogEnabled(mContext.getPackageName()); } catch (RemoteException re) { - re.rethrowFromSystemServer(); - // unreachable - return false; + throw re.rethrowFromSystemServer(); } } @@ -14102,8 +14100,8 @@ public class DevicePolicyManager { * is enforced by the caller. Disabling the policy clears the callback. Each time a new callback * is set, it will first be invoked with all the audit log events available at the time. * - * @param callback callback to invoke when new audit log events become available or {@code null} - * to clear the callback. + * @param callback The callback to invoke when new audit log events become available. + * @param executor The executor through which the callback should be invoked. * @hide */ @SystemApi @@ -14111,11 +14109,10 @@ public class DevicePolicyManager { @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback( @NonNull @CallbackExecutor Executor executor, - @Nullable Consumer<List<SecurityEvent>> callback) { + @NonNull Consumer<List<SecurityEvent>> callback) { throwIfParentInstance("setAuditLogEventCallback"); - final IAuditLogEventsCallback wrappedCallback = callback == null - ? null - : new IAuditLogEventsCallback.Stub() { + final IAuditLogEventsCallback wrappedCallback = + new IAuditLogEventsCallback.Stub() { @Override public void onNewAuditLogEvents(List<SecurityEvent> events) { executor.execute(() -> callback.accept(events)); @@ -14124,7 +14121,25 @@ public class DevicePolicyManager { try { mService.setAuditLogEventsCallback(mContext.getPackageName(), wrappedCallback); } catch (RemoteException re) { - re.rethrowFromSystemServer(); + throw re.rethrowFromSystemServer(); + } + } + + /** + * Clears audit log event callback. If a callback was set previously, it may still get invoked + * after this call returns if it was already scheduled. + * + * @hide + */ + @SystemApi + @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED) + @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) + public void clearAuditLogEventCallback() { + throwIfParentInstance("clearAuditLogEventCallback"); + try { + mService.setAuditLogEventsCallback(mContext.getPackageName(), null); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); } } @@ -17442,24 +17457,24 @@ public class DevicePolicyManager { } /** - * Returns the subscription ids of all subscriptions which was downloaded by the calling + * Returns the subscription ids of all subscriptions which were downloaded by the calling * admin. * * <p> This returns only the subscriptions which were downloaded by the calling admin via * {@link android.telephony.euicc.EuiccManager#downloadSubscription}. - * If a susbcription is returned by this method then in it subject to management controls + * If a subscription is returned by this method then in it subject to management controls * and cannot be removed by users. * * <p> Callable by device owners and profile owners. * - * @throws SecurityException if the caller is not authorized to call this method - * @return ids of all managed subscriptions currently downloaded by an admin on the device + * @throws SecurityException if the caller is not authorized to call this method. + * @return ids of all managed subscriptions currently downloaded by an admin on the device. */ @FlaggedApi(FLAG_ESIM_MANAGEMENT_ENABLED) @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS) @NonNull - public Set<Integer> getSubscriptionsIds() { - throwIfParentInstance("getSubscriptionsIds"); + public Set<Integer> getSubscriptionIds() { + throwIfParentInstance("getSubscriptionIds"); if (mService != null) { try { return intArrayToSet(mService.getSubscriptionIds(mContext.getPackageName())); diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index 89199ca0d493..75485626d2ef 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -1297,7 +1297,7 @@ public class AssistStructure implements Parcelable { */ @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) @Nullable - public GetCredentialRequest getCredentialManagerRequest() { + public GetCredentialRequest getPendingCredentialRequest() { return mGetCredentialRequest; } @@ -1306,7 +1306,7 @@ public class AssistStructure implements Parcelable { */ @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) @Nullable - public ResultReceiver getCredentialManagerCallback() { + public ResultReceiver getPendingCredentialCallback() { return mGetCredentialResultReceiver; } @@ -2191,14 +2191,14 @@ public class AssistStructure implements Parcelable { @Nullable @Override - public GetCredentialRequest getCredentialManagerRequest() { + public GetCredentialRequest getPendingCredentialRequest() { return mNode.mGetCredentialRequest; } @Nullable @Override public OutcomeReceiver< - GetCredentialResponse, GetCredentialException> getCredentialManagerCallback() { + GetCredentialResponse, GetCredentialException> getPendingCredentialCallback() { return mNode.mGetCredentialCallback; } @@ -2267,7 +2267,7 @@ public class AssistStructure implements Parcelable { } @Override - public void setCredentialManagerRequest(@NonNull GetCredentialRequest request, + public void setPendingCredentialRequest(@NonNull GetCredentialRequest request, @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) { mNode.mGetCredentialRequest = request; mNode.mGetCredentialCallback = callback; @@ -2654,7 +2654,7 @@ public class AssistStructure implements Parcelable { + ", isCredential=" + node.isCredential() ); } - GetCredentialRequest getCredentialRequest = node.getCredentialManagerRequest(); + GetCredentialRequest getCredentialRequest = node.getPendingCredentialRequest(); if (getCredentialRequest == null) { Log.i(TAG, prefix + " No Credential Manager Request"); } else { diff --git a/core/java/android/app/network-policy.aconfig b/core/java/android/app/network-policy.aconfig new file mode 100644 index 000000000000..88f386f6025d --- /dev/null +++ b/core/java/android/app/network-policy.aconfig @@ -0,0 +1,11 @@ +package: "android.app" + +flag { + namespace: "backstage_power" + name: "clear_dns_cache_on_network_rules_update" + description: "Clears the DNS cache when the network rules update" + bug: "237556596" + metadata { + purpose: PURPOSE_BUGFIX + } +}
\ No newline at end of file diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index cda4d89b828f..2c0e035e80c4 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -822,7 +822,18 @@ public class AppWidgetManager { * * @param appWidgetIds The AppWidget instances to notify of view data changes. * @param viewId The collection view id. - */ + * @deprecated The corresponding API + * {@link RemoteViews#setRemoteAdapter(int, Intent)} associated with this method has been + * deprecated. Moving forward please use + * {@link RemoteViews#setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)} + * instead to set {@link android.widget.RemoteViews.RemoteCollectionItems} for the remote + * adapter and update the widget views by calling {@link #updateAppWidget(int[], RemoteViews)}, + * {@link #updateAppWidget(int, RemoteViews)}, + * {@link #updateAppWidget(ComponentName, RemoteViews)}, + * {@link #partiallyUpdateAppWidget(int[], RemoteViews)}, + * or {@link #partiallyUpdateAppWidget(int, RemoteViews)}, whichever applicable. + */ + @Deprecated public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) { if (mService == null) { return; @@ -873,7 +884,18 @@ public class AppWidgetManager { * * @param appWidgetId The AppWidget instance to notify of view data changes. * @param viewId The collection view id. - */ + * @deprecated The corresponding API + * {@link RemoteViews#setRemoteAdapter(int, Intent)} associated with this method has been + * deprecated. Moving forward please use + * {@link RemoteViews#setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)} + * instead to set {@link android.widget.RemoteViews.RemoteCollectionItems} for the remote + * adapter and update the widget views by calling {@link #updateAppWidget(int[], RemoteViews)}, + * {@link #updateAppWidget(int, RemoteViews)}, + * {@link #updateAppWidget(ComponentName, RemoteViews)}, + * {@link #partiallyUpdateAppWidget(int[], RemoteViews)}, + * or {@link #partiallyUpdateAppWidget(int, RemoteViews)}, whichever applicable. + */ + @Deprecated public void notifyAppWidgetViewDataChanged(int appWidgetId, int viewId) { if (mService == null) { return; diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index 533fa512dae8..55957bf887f8 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -133,4 +133,7 @@ interface ILauncherApps { void setArchiveCompatibilityOptions(boolean enableIconOverlay, boolean enableUnarchivalConfirmation); List<UserHandle> getUserProfiles(); + + /** Saves view capture data to the wm trace directory. */ + void saveViewCaptureData(); } diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 41c1f17ce978..3a5383d9537b 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -1328,6 +1328,19 @@ public class LauncherApps { } /** + * Saves view capture data to the default location. + * @hide + */ + @RequiresPermission(READ_FRAME_BUFFER) + public void saveViewCaptureData() { + try { + mService.saveViewCaptureData(); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + /** * Unregister a callback, so that it won't be called when LauncherApps dumps. * @hide */ diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig index f660770d2fc8..7fd0b03b213d 100644 --- a/core/java/android/content/res/flags.aconfig +++ b/core/java/android/content/res/flags.aconfig @@ -38,7 +38,7 @@ flag { name: "nine_patch_frro" namespace: "resource_manager" description: "Feature flag for creating an frro from a 9-patch" - bug: "309232726" + bug: "296324826" } flag { diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java index a3a2a2e6fd16..c5167dbc7d4c 100644 --- a/core/java/android/hardware/radio/ProgramList.java +++ b/core/java/android/hardware/radio/ProgramList.java @@ -304,7 +304,11 @@ public final class ProgramList implements AutoCloseable { * * @param id primary identifier of a program to fetch * @return the program info, or null if there is no such program on the list + * + * @deprecated Use {@link #getProgramInfos(ProgramSelector.Identifier)} to get all programs + * with the given primary identifier */ + @Deprecated public @Nullable RadioManager.ProgramInfo get(@NonNull ProgramSelector.Identifier id) { Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries; synchronized (mLock) { diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java index a968c6f0ad05..0740374ad8e2 100644 --- a/core/java/android/hardware/radio/ProgramSelector.java +++ b/core/java/android/hardware/radio/ProgramSelector.java @@ -312,15 +312,23 @@ public final class ProgramSelector implements Parcelable { public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; /** * 1: AM, 2:FM + * @deprecated use {@link #IDENTIFIER_TYPE_DRMO_FREQUENCY} instead */ + @Deprecated public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; /** * 32bit primary identifier for SiriusXM Satellite Radio. + * + * @deprecated SiriusXM Satellite Radio is not supported */ + @Deprecated public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; /** * 0-999 range + * + * @deprecated SiriusXM Satellite Radio is not supported */ + @Deprecated public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; /** * 44bit compound primary identifier for Digital Audio Broadcasting and diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java index 61cf8901c454..da6c68646820 100644 --- a/core/java/android/hardware/radio/RadioManager.java +++ b/core/java/android/hardware/radio/RadioManager.java @@ -166,7 +166,12 @@ public class RadioManager { * analog handover state managed from the HAL implementation side. * * <p>Some radio technologies may not support this, i.e. DAB. + * + * @deprecated Use {@link #CONFIG_FORCE_ANALOG_FM} instead. If {@link #CONFIG_FORCE_ANALOG_FM} + * is supported in HAL, {@link RadioTuner#setConfigFlag} and {@link RadioTuner#isConfigFlagSet} + * with CONFIG_FORCE_ANALOG will set/get the value of {@link #CONFIG_FORCE_ANALOG_FM}. */ + @Deprecated public static final int CONFIG_FORCE_ANALOG = 2; /** * Forces the digital playback for the supporting radio technology. diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl index dd8b3deabc01..09e6b5de8a3c 100644 --- a/core/java/android/service/dreams/IDreamManager.aidl +++ b/core/java/android/service/dreams/IDreamManager.aidl @@ -38,6 +38,8 @@ interface IDreamManager { boolean isDreaming(); @UnsupportedAppUsage boolean isDreamingOrInPreview(); + @UnsupportedAppUsage + boolean canStartDreaming(boolean isScreenOn); void finishSelf(in IBinder token, boolean immediate); void startDozing(in IBinder token, int screenState, int screenBrightness); void stopDozing(in IBinder token); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 9fab1f7d8f2e..a5ff48fd3ca6 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -7057,16 +7057,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Clears the request and callback previously set - * through {@link View#setCredentialManagerRequest}. + * through {@link View#setPendingCredentialRequest}. * Once this API is invoked, there will be no request fired to {@link CredentialManager} * on future view focus events. * - * @see #setCredentialManagerRequest + * @see #setPendingCredentialRequest */ @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) - public void clearCredentialManagerRequest() { + public void clearPendingCredentialRequest() { if (Log.isLoggable(AUTOFILL_LOG_TAG, Log.VERBOSE)) { - Log.v(AUTOFILL_LOG_TAG, "clearCredentialManagerRequest called"); + Log.v(AUTOFILL_LOG_TAG, "clearPendingCredentialRequest called"); } mViewCredentialHandler = null; } @@ -7096,7 +7096,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * propagated for the given view */ @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) - public void setCredentialManagerRequest(@NonNull GetCredentialRequest request, + public void setPendingCredentialRequest(@NonNull GetCredentialRequest request, @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) { Preconditions.checkNotNull(request, "request must not be null"); Preconditions.checkNotNull(callback, "request must not be null"); @@ -9604,7 +9604,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, structure.setIsCredential(isCredential()); } if (getViewCredentialHandler() != null) { - structure.setCredentialManagerRequest( + structure.setPendingCredentialRequest( getViewCredentialHandler().getRequest(), getViewCredentialHandler().getCallback()); } @@ -10009,22 +10009,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ public void onGetCredentialResponse(GetCredentialResponse response) { - if (getCredentialManagerCallback() == null) { + if (getPendingCredentialCallback() == null) { Log.w(AUTOFILL_LOG_TAG, "onGetCredentialResponse called but no callback found"); return; } - getCredentialManagerCallback().onResult(response); + getPendingCredentialCallback().onResult(response); } /** * @hide */ public void onGetCredentialException(String errorType, String errorMsg) { - if (getCredentialManagerCallback() == null) { + if (getPendingCredentialCallback() == null) { Log.w(AUTOFILL_LOG_TAG, "onGetCredentialException called but no callback found"); return; } - getCredentialManagerCallback().onError(new GetCredentialException(errorType, errorMsg)); + getPendingCredentialCallback().onError(new GetCredentialException(errorType, errorMsg)); } /** @@ -10055,13 +10055,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * the active {@link android.service.autofill.AutofillService} on * the device. * - * <p>See {@link #setCredentialManagerRequest} for more info. + * <p>See {@link #setPendingCredentialRequest} for more info. * * @return The credential request associated with this View. */ @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) @Nullable - public final GetCredentialRequest getCredentialManagerRequest() { + public final GetCredentialRequest getPendingCredentialRequest() { if (mViewCredentialHandler == null) { return null; } @@ -10071,14 +10071,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Returns the callback that has previously been set up on this view through - * the {@link #setCredentialManagerRequest} API. + * the {@link #setPendingCredentialRequest} API. * If the return value is null, that means no callback, or request, has been set * on the view and no {@link CredentialManager} flow will be invoked * when this view is focused. Traditioanl autofill flows will still * work, and autofillable content will still be returned through the * {@link #autofill(AutofillValue)} )} API. * - * <p>See {@link #setCredentialManagerRequest} for more info. + * <p>See {@link #setPendingCredentialRequest} for more info. * * @return The callback associated with this view that will be invoked on a response from * {@link CredentialManager} . @@ -10086,7 +10086,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) @Nullable public final OutcomeReceiver<GetCredentialResponse, - GetCredentialException> getCredentialManagerCallback() { + GetCredentialException> getPendingCredentialCallback() { if (mViewCredentialHandler == null) { return null; } @@ -11042,7 +11042,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()))); } if (getViewCredentialHandler() != null) { - structure.setCredentialManagerRequest( + structure.setPendingCredentialRequest( getViewCredentialHandler().getRequest(), getViewCredentialHandler().getCallback()); } diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java index 1efd37591ee4..6c852c3a2d3f 100644 --- a/core/java/android/view/ViewStructure.java +++ b/core/java/android/view/ViewStructure.java @@ -361,11 +361,11 @@ public abstract class ViewStructure { * {@link android.credentials.CredentialManager} request will be fired when this * node is focused. * <p> For details on how a request and callback can be set, see - * {@link ViewStructure#setCredentialManagerRequest(GetCredentialRequest, OutcomeReceiver)} + * {@link ViewStructure#setPendingCredentialRequest(GetCredentialRequest, OutcomeReceiver)} */ @Nullable @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) - public GetCredentialRequest getCredentialManagerRequest() { + public GetCredentialRequest getPendingCredentialRequest() { return null; } @@ -376,12 +376,12 @@ public abstract class ViewStructure { * {@link android.credentials.CredentialManager} request will be fired when this * node is focused. * <p> For details on how a request and callback can be set, see - * {@link ViewStructure#setCredentialManagerRequest(GetCredentialRequest, OutcomeReceiver)} + * {@link ViewStructure#setPendingCredentialRequest(GetCredentialRequest, OutcomeReceiver)} */ @Nullable @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) public OutcomeReceiver< - GetCredentialResponse, GetCredentialException> getCredentialManagerCallback() { + GetCredentialResponse, GetCredentialException> getPendingCredentialCallback() { return null; } @@ -555,12 +555,12 @@ public abstract class ViewStructure { * @param callback the callback where the response or exception, is returned */ @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) - public void setCredentialManagerRequest(@NonNull GetCredentialRequest request, + public void setPendingCredentialRequest(@NonNull GetCredentialRequest request, @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {} /** * Clears the credential request previously set through - * {@link ViewStructure#setCredentialManagerRequest(GetCredentialRequest, OutcomeReceiver)} + * {@link ViewStructure#setPendingCredentialRequest(GetCredentialRequest, OutcomeReceiver)} */ @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) public void clearCredentialManagerRequest() {} diff --git a/core/java/com/android/internal/compat/Android.bp b/core/java/com/android/internal/compat/Android.bp new file mode 100644 index 000000000000..9ff05a6589b7 --- /dev/null +++ b/core/java/com/android/internal/compat/Android.bp @@ -0,0 +1,7 @@ +aconfig_declarations { + name: "compat_logging_flags", + package: "com.android.internal.compat.flags", + srcs: [ + "compat_logging_flags.aconfig", + ], +} diff --git a/core/java/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java index b9d3df678a91..6ff546fd77f8 100644 --- a/core/java/com/android/internal/compat/ChangeReporter.java +++ b/core/java/com/android/internal/compat/ChangeReporter.java @@ -24,6 +24,7 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.compat.flags.Flags; import com.android.internal.util.FrameworkStatsLog; import java.lang.annotation.Retention; @@ -40,7 +41,7 @@ import java.util.Set; * @hide */ public final class ChangeReporter { - private static final String TAG = "CompatibilityChangeReporter"; + private static final String TAG = "CompatChangeReporter"; private int mSource; private static final class ChangeReport { @@ -84,22 +85,37 @@ public final class ChangeReporter { * Report the change to stats log and to the debug log if the change was not previously * logged already. * - * @param uid affected by the change - * @param changeId the reported change id - * @param state of the reported change - enabled/disabled/only logged + * @param uid affected by the change + * @param changeId the reported change id + * @param state of the reported change - enabled/disabled/only logged + * @param isLoggableBySdk whether debug logging is allowed for this change based on target + * SDK version. This is combined with other logic to determine whether to + * actually log. If the sdk version does not matter, should be true. */ - public void reportChange(int uid, long changeId, int state) { + public void reportChange(int uid, long changeId, int state, boolean isLoggableBySdk) { if (shouldWriteToStatsLog(uid, changeId, state)) { FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid, changeId, state, mSource); } - if (shouldWriteToDebug(uid, changeId, state)) { + if (shouldWriteToDebug(uid, changeId, state, isLoggableBySdk)) { debugLog(uid, changeId, state); } markAsReported(uid, new ChangeReport(changeId, state)); } /** + * Report the change to stats log and to the debug log if the change was not previously + * logged already. + * + * @param uid affected by the change + * @param changeId the reported change id + * @param state of the reported change - enabled/disabled/only logged + */ + public void reportChange(int uid, long changeId, int state) { + reportChange(uid, changeId, state, true); + } + + /** * Start logging all the time to logcat. */ public void startDebugLogAll() { @@ -130,14 +146,43 @@ public final class ChangeReporter { /** * Returns whether the next report should be logged to logcat. * - * @param uid affected by the change - * @param changeId the reported change id - * @param state of the reported change - enabled/disabled/only logged + * @param uid affected by the change + * @param changeId the reported change id + * @param state of the reported change - enabled/disabled/only logged + * @param isLoggableBySdk whether debug logging is allowed for this change based on target + * SDK version. This is combined with other logic to determine whether to + * actually log. If the sdk version does not matter, should be true. + * @return true if the report should be logged + */ + @VisibleForTesting + public boolean shouldWriteToDebug( + int uid, long changeId, int state, boolean isLoggableBySdk) { + // If log all bit is on, always return true. + if (mDebugLogAll) return true; + // If the change has already been reported, do not write. + if (isAlreadyReported(uid, new ChangeReport(changeId, state))) return false; + + // If the flag is turned off or the TAG's logging is forced to debug level with + // `adb setprop log.tag.CompatChangeReporter=DEBUG`, write to debug since the above checks + // have already passed. + boolean skipLoggingFlag = Flags.skipOldAndDisabledCompatLogging(); + if (!skipLoggingFlag || Log.isLoggable(TAG, Log.DEBUG)) return true; + + // Log if the change is enabled and targets the latest sdk version. + return isLoggableBySdk && state != STATE_DISABLED; + } + + /** + * Returns whether the next report should be logged to logcat. + * + * @param uid affected by the change + * @param changeId the reported change id + * @param state of the reported change - enabled/disabled/only logged * @return true if the report should be logged */ @VisibleForTesting public boolean shouldWriteToDebug(int uid, long changeId, int state) { - return mDebugLogAll || !isAlreadyReported(uid, new ChangeReport(changeId, state)); + return shouldWriteToDebug(uid, changeId, state, true); } private boolean isAlreadyReported(int uid, ChangeReport report) { diff --git a/core/java/com/android/internal/compat/compat_logging_flags.aconfig b/core/java/com/android/internal/compat/compat_logging_flags.aconfig new file mode 100644 index 000000000000..fab3856daca7 --- /dev/null +++ b/core/java/com/android/internal/compat/compat_logging_flags.aconfig @@ -0,0 +1,9 @@ +package: "com.android.internal.compat.flags" + +flag { + name: "skip_old_and_disabled_compat_logging" + namespace: "platform_compat" + description: "Feature flag for skipping debug logging for changes that do not target the latest sdk or are disabled" + bug: "323949942" + is_fixed_read_only: true +}
\ No newline at end of file diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index dc3b5a8846cf..025703364f0f 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -33,8 +33,10 @@ import com.android.internal.inputmethod.IRemoteInputConnection; import com.android.internal.inputmethod.InputBindResult; /** - * Public interface to the global input method manager, used by all client - * applications. + * Public interface to the global input method manager, used by all client applications. + * + * When adding new methods, make sure the associated user can be inferred from the arguments. + * Consider passing the associated userId when not already passing a display id or a window token. */ interface IInputMethodManager { void addClient(in IInputMethodClient client, in IRemoteInputConnection inputmethod, diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java index a0346538ddc5..10ac05d66c62 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java @@ -88,13 +88,6 @@ public final class RadioModuleTest { } @Test - public void setInternalHalCallback_callbackSetInHal() throws Exception { - mRadioModule.setInternalHalCallback(); - - verify(mBroadcastRadioMock).setTunerCallback(any()); - } - - @Test public void getImage_withValidIdFromRadioModule() { int imageId = 1; diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java index 262f167078e2..755bcdb7df20 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java @@ -192,66 +192,6 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0]; return null; }).when(mBroadcastRadioMock).setTunerCallback(any()); - mRadioModule.setInternalHalCallback(); - - doAnswer(invocation -> { - android.hardware.broadcastradio.ProgramSelector halSel = - (android.hardware.broadcastradio.ProgramSelector) invocation.getArguments()[0]; - mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(halSel, SIGNAL_QUALITY); - if (halSel.primaryId.type != IdentifierType.AMFM_FREQUENCY_KHZ) { - throw new ServiceSpecificException(Result.NOT_SUPPORTED); - } - mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo); - return Result.OK; - }).when(mBroadcastRadioMock).tune(any()); - - doAnswer(invocation -> { - if ((boolean) invocation.getArguments()[0]) { - mHalCurrentInfo.selector.primaryId.value += AM_FM_FREQUENCY_SPACING; - } else { - mHalCurrentInfo.selector.primaryId.value -= AM_FM_FREQUENCY_SPACING; - } - mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId; - mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId; - mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo); - return Result.OK; - }).when(mBroadcastRadioMock).step(anyBoolean()); - - doAnswer(invocation -> { - if (mHalCurrentInfo == null) { - android.hardware.broadcastradio.ProgramSelector placeHolderSelector = - AidlTestUtils.makeHalFmSelector(/* freq= */ 97300); - - mHalTunerCallback.onTuneFailed(Result.TIMEOUT, placeHolderSelector); - return Result.OK; - } - mHalCurrentInfo.selector.primaryId.value = getSeekFrequency( - mHalCurrentInfo.selector.primaryId.value, - !(boolean) invocation.getArguments()[0]); - mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId; - mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId; - mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo); - return Result.OK; - }).when(mBroadcastRadioMock).seek(anyBoolean(), anyBoolean()); - - doReturn(null).when(mBroadcastRadioMock).getImage(anyInt()); - - doAnswer(invocation -> { - int configFlag = (int) invocation.getArguments()[0]; - if (configFlag == UNSUPPORTED_CONFIG_FLAG) { - throw new ServiceSpecificException(Result.NOT_SUPPORTED); - } - return mHalConfigMap.getOrDefault(configFlag, false); - }).when(mBroadcastRadioMock).isConfigFlagSet(anyInt()); - - doAnswer(invocation -> { - int configFlag = (int) invocation.getArguments()[0]; - if (configFlag == UNSUPPORTED_CONFIG_FLAG) { - throw new ServiceSpecificException(Result.NOT_SUPPORTED); - } - mHalConfigMap.put(configFlag, (boolean) invocation.getArguments()[1]); - return null; - }).when(mBroadcastRadioMock).setConfigFlag(anyInt(), anyBoolean()); } @After @@ -330,6 +270,7 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { expect.withMessage("Close state of broadcast radio service session") .that(mTunerSessions[0].isClosed()).isTrue(); + verify(mBroadcastRadioMock).unsetTunerCallback(); } @Test @@ -351,6 +292,7 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { .that(mTunerSessions[index].isClosed()).isFalse(); } } + verify(mBroadcastRadioMock, never()).unsetTunerCallback(); } @Test @@ -378,6 +320,7 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { expect.withMessage("Close state of broadcast radio service session of index %s", index) .that(mTunerSessions[index].isClosed()).isTrue(); } + verify(mBroadcastRadioMock).unsetTunerCallback(); } @Test @@ -1295,6 +1238,71 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { mAidlTunerCallbackMocks[index] = mock(android.hardware.radio.ITunerCallback.class); mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index]); } + setupMockedHalTunerSession(); + } + + private void setupMockedHalTunerSession() throws Exception { + expect.withMessage("Registered HAL tuner callback").that(mHalTunerCallback) + .isNotNull(); + + doAnswer(invocation -> { + android.hardware.broadcastradio.ProgramSelector halSel = + (android.hardware.broadcastradio.ProgramSelector) invocation.getArguments()[0]; + mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(halSel, SIGNAL_QUALITY); + if (halSel.primaryId.type != IdentifierType.AMFM_FREQUENCY_KHZ) { + throw new ServiceSpecificException(Result.NOT_SUPPORTED); + } + mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo); + return Result.OK; + }).when(mBroadcastRadioMock).tune(any()); + + doAnswer(invocation -> { + if ((boolean) invocation.getArguments()[0]) { + mHalCurrentInfo.selector.primaryId.value += AM_FM_FREQUENCY_SPACING; + } else { + mHalCurrentInfo.selector.primaryId.value -= AM_FM_FREQUENCY_SPACING; + } + mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId; + mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId; + mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo); + return Result.OK; + }).when(mBroadcastRadioMock).step(anyBoolean()); + + doAnswer(invocation -> { + if (mHalCurrentInfo == null) { + android.hardware.broadcastradio.ProgramSelector placeHolderSelector = + AidlTestUtils.makeHalFmSelector(/* freq= */ 97300); + + mHalTunerCallback.onTuneFailed(Result.TIMEOUT, placeHolderSelector); + return Result.OK; + } + mHalCurrentInfo.selector.primaryId.value = getSeekFrequency( + mHalCurrentInfo.selector.primaryId.value, + !(boolean) invocation.getArguments()[0]); + mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId; + mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId; + mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo); + return Result.OK; + }).when(mBroadcastRadioMock).seek(anyBoolean(), anyBoolean()); + + doReturn(null).when(mBroadcastRadioMock).getImage(anyInt()); + + doAnswer(invocation -> { + int configFlag = (int) invocation.getArguments()[0]; + if (configFlag == UNSUPPORTED_CONFIG_FLAG) { + throw new ServiceSpecificException(Result.NOT_SUPPORTED); + } + return mHalConfigMap.getOrDefault(configFlag, false); + }).when(mBroadcastRadioMock).isConfigFlagSet(anyInt()); + + doAnswer(invocation -> { + int configFlag = (int) invocation.getArguments()[0]; + if (configFlag == UNSUPPORTED_CONFIG_FLAG) { + throw new ServiceSpecificException(Result.NOT_SUPPORTED); + } + mHalConfigMap.put(configFlag, (boolean) invocation.getArguments()[1]); + return null; + }).when(mBroadcastRadioMock).setConfigFlag(anyInt(), anyBoolean()); } private long getSeekFrequency(long currentFrequency, boolean seekDown) { diff --git a/core/tests/PlatformCompatFramework/Android.bp b/core/tests/PlatformCompatFramework/Android.bp index 95e23ad396af..2621d280bd9d 100644 --- a/core/tests/PlatformCompatFramework/Android.bp +++ b/core/tests/PlatformCompatFramework/Android.bp @@ -18,6 +18,7 @@ android_test { static_libs: [ "junit", "androidx.test.rules", + "flag-junit", ], platform_apis: true, } diff --git a/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java b/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java index a052543c6446..12a42f975cd7 100644 --- a/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java +++ b/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java @@ -19,9 +19,17 @@ package com.android.internal.compat; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import android.platform.test.flag.junit.SetFlagsRule; + +import com.android.internal.compat.flags.Flags; + +import org.junit.Rule; import org.junit.Test; public class ChangeReporterTest { + + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Test public void testStatsLogOnce() { ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE); @@ -63,7 +71,7 @@ public class ChangeReporterTest { ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE); int myUid = 1022, otherUid = 1023; long myChangeId = 500L, otherChangeId = 600L; - int myState = ChangeReporter.STATE_ENABLED, otherState = ChangeReporter.STATE_DISABLED; + int myState = ChangeReporter.STATE_ENABLED, otherState = ChangeReporter.STATE_LOGGED; assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myState)); reporter.reportChange(myUid, myChangeId, myState); @@ -112,4 +120,80 @@ public class ChangeReporterTest { reporter.stopDebugLogAll(); assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myState)); } + + @Test + public void testDebugLogWithFlagOnAndOldSdk() { + mSetFlagsRule.enableFlags(Flags.FLAG_SKIP_OLD_AND_DISABLED_COMPAT_LOGGING); + ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE); + int myUid = 1022; + long myChangeId = 500L; + int myEnabledState = ChangeReporter.STATE_ENABLED; + int myDisabledState = ChangeReporter.STATE_DISABLED; + + // Report will not log if target sdk is before the previous version. + assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, false)); + + reporter.resetReportedChanges(myUid); + + // Report will be logged if target sdk is the latest version. + assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, true)); + + reporter.resetReportedChanges(myUid); + + // If the report is disabled, the sdk version shouldn't matter. + assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, true)); + } + + @Test + public void testDebugLogWithFlagOnAndDisabledChange() { + mSetFlagsRule.enableFlags(Flags.FLAG_SKIP_OLD_AND_DISABLED_COMPAT_LOGGING); + ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE); + int myUid = 1022; + long myChangeId = 500L; + int myEnabledState = ChangeReporter.STATE_ENABLED; + int myDisabledState = ChangeReporter.STATE_DISABLED; + + // Report will not log if the change is disabled. + assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, true)); + + reporter.resetReportedChanges(myUid); + + // Report will be logged if the change is enabled. + assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, true)); + + reporter.resetReportedChanges(myUid); + + // If the report is not the latest version, the disabled state doesn't matter. + assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, false)); + } + + @Test + public void testDebugLogWithFlagOff() { + mSetFlagsRule.disableFlags(Flags.FLAG_SKIP_OLD_AND_DISABLED_COMPAT_LOGGING); + ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE); + int myUid = 1022; + long myChangeId = 500L; + int myEnabledState = ChangeReporter.STATE_ENABLED; + int myDisabledState = ChangeReporter.STATE_DISABLED; + + // Report will be logged even if the change is not the latest sdk but the flag is off. + assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, false)); + + reporter.resetReportedChanges(myUid); + + // Report will be logged if the change is enabled and the latest sdk but the flag is off. + assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, true)); + + reporter.resetReportedChanges(myUid); + + // Report will be logged if the change is disabled and the latest sdk but the flag is + // off. + assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, true)); + + reporter.resetReportedChanges(myUid); + + // Report will be logged if the change is disabled and not the latest sdk but the flag is + // off. + assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, false)); + } } diff --git a/core/tests/coretests/src/android/app/assist/AssistStructureTest.java b/core/tests/coretests/src/android/app/assist/AssistStructureTest.java index abeb08caf23d..1f2788c4b11b 100644 --- a/core/tests/coretests/src/android/app/assist/AssistStructureTest.java +++ b/core/tests/coretests/src/android/app/assist/AssistStructureTest.java @@ -266,8 +266,8 @@ public class AssistStructureTest { assertThat(view.getViewRootImpl()).isNotNull(); ViewNodeBuilder viewStructure = new ViewNodeBuilder(); viewStructure.setAutofillId(view.getAutofillId()); - viewStructure.setCredentialManagerRequest(view.getCredentialManagerRequest(), - view.getCredentialManagerCallback()); + viewStructure.setPendingCredentialRequest(view.getPendingCredentialRequest(), + view.getPendingCredentialCallback()); view.onProvideAutofillStructure(viewStructure, /* flags= */ 0); ViewNodeParcelable viewNodeParcelable = new ViewNodeParcelable(viewStructure.getViewNode()); @@ -289,17 +289,20 @@ public class AssistStructureTest { assertThat(view.getViewRootImpl()).isNotNull(); ViewNodeBuilder viewStructure = new ViewNodeBuilder(); - viewStructure.setCredentialManagerRequest(view.getCredentialManagerRequest(), - view.getCredentialManagerCallback()); + if (view.getPendingCredentialRequest() != null + && view.getPendingCredentialCallback() != null) { + viewStructure.setPendingCredentialRequest(view.getPendingCredentialRequest(), + view.getPendingCredentialCallback()); + } - assertEquals(viewStructure.getCredentialManagerRequest(), GET_CREDENTIAL_REQUEST); - assertEquals(viewStructure.getCredentialManagerCallback(), + assertEquals(viewStructure.getPendingCredentialRequest(), GET_CREDENTIAL_REQUEST); + assertEquals(viewStructure.getPendingCredentialCallback(), GET_CREDENTIAL_REQUEST_CALLBACK); viewStructure.clearCredentialManagerRequest(); - assertNull(viewStructure.getCredentialManagerRequest()); - assertNull(viewStructure.getCredentialManagerCallback()); + assertNull(viewStructure.getPendingCredentialRequest()); + assertNull(viewStructure.getPendingCredentialCallback()); } @Test @@ -386,14 +389,14 @@ public class AssistStructureTest { EditText view = new EditText(mContext); view.setText("Big Hint in Little View"); view.setAutofillHints(BIG_STRING); - view.setCredentialManagerRequest(GET_CREDENTIAL_REQUEST, GET_CREDENTIAL_REQUEST_CALLBACK); + view.setPendingCredentialRequest(GET_CREDENTIAL_REQUEST, GET_CREDENTIAL_REQUEST_CALLBACK); return view; } private EditText newCredentialView() { EditText view = new EditText(mContext); view.setText("Credential Request"); - view.setCredentialManagerRequest(GET_CREDENTIAL_REQUEST, GET_CREDENTIAL_REQUEST_CALLBACK); + view.setPendingCredentialRequest(GET_CREDENTIAL_REQUEST, GET_CREDENTIAL_REQUEST_CALLBACK); return view; } @@ -421,8 +424,8 @@ public class AssistStructureTest { assertThat(view.getAutofillId()).isNotNull(); assertThat(view.getText().toString()).isEqualTo("Big Hint in Little View"); - assertThat(view.getCredentialManagerRequest()).isEqualTo(GET_CREDENTIAL_REQUEST); - assertThat(view.getCredentialManagerCallback()).isEqualTo(GET_CREDENTIAL_REQUEST_CALLBACK); + assertThat(view.getPendingCredentialRequest()).isEqualTo(GET_CREDENTIAL_REQUEST); + assertThat(view.getPendingCredentialCallback()).isEqualTo(GET_CREDENTIAL_REQUEST_CALLBACK); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 474430eb44ab..23bdd08e6b24 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -2474,11 +2474,12 @@ public class BubbleStackView extends FrameLayout // Let the expanded animation controller know that it shouldn't animate child adds/reorders // since we're about to animate collapsed. mExpandedAnimationController.notifyPreparingToCollapse(); - + final PointF collapsePosition = mStackAnimationController + .getStackPositionAlongNearestHorizontalEdge(); updateOverflowDotVisibility(false /* expanding */); final Runnable collapseBackToStack = () -> mExpandedAnimationController.collapseBackToStack( - mStackAnimationController.getStackPositionAlongNearestHorizontalEdge(), + collapsePosition, /* fadeBubblesDuringCollapse= */ mRemovingLastBubbleWhileExpanded, () -> { mBubbleContainer.setActiveController(mStackAnimationController); @@ -2501,7 +2502,8 @@ public class BubbleStackView extends FrameLayout } mExpandedViewAnimationController.reset(); }; - mExpandedViewAnimationController.animateCollapse(collapseBackToStack, after); + mExpandedViewAnimationController.animateCollapse(collapseBackToStack, after, + collapsePosition); if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { // When the animation completes, we should no longer be showing the content. // This won't actually update content visibility immediately, if we are currently diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java index 8a33780bc8d5..41755293f382 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java @@ -15,6 +15,8 @@ */ package com.android.wm.shell.bubbles.animation; +import android.graphics.PointF; + import com.android.wm.shell.bubbles.BubbleExpandedView; /** @@ -55,8 +57,9 @@ public interface ExpandedViewAnimationController { * @param startStackCollapse runnable that is triggered when bubbles can start moving back to * their collapsed location * @param after runnable to run after animation is complete + * @param collapsePosition the position on screen the stack will collapse to */ - void animateCollapse(Runnable startStackCollapse, Runnable after); + void animateCollapse(Runnable startStackCollapse, Runnable after, PointF collapsePosition); /** * Animate the view back to fully expanded state. @@ -69,6 +72,22 @@ public interface ExpandedViewAnimationController { void animateForImeVisibilityChange(boolean visible); /** + * Whether this controller should also animate the expansion for the bubble + */ + boolean shouldAnimateExpansion(); + + /** + * Animate the expansion of the bubble. + * + * @param startDelayMillis how long to delay starting the expansion animation + * @param after runnable to run after the animation is complete + * @param collapsePosition the position on screen the stack will collapse to (and expand from) + * @param bubblePosition the position of the bubble on screen that the view is associated with + */ + void animateExpansion(long startDelayMillis, Runnable after, PointF collapsePosition, + PointF bubblePosition); + + /** * Reset the view to fully expanded state */ void reset(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java index e43609fe8ff0..aa4129a14dbc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java @@ -28,6 +28,7 @@ import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.content.Context; +import android.graphics.PointF; import android.view.HapticFeedbackConstants; import android.view.ViewConfiguration; @@ -187,9 +188,11 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio } @Override - public void animateCollapse(Runnable startStackCollapse, Runnable after) { - ProtoLog.d(WM_SHELL_BUBBLES, "expandedView animate collapse swipeVel=%f minFlingVel=%d", - mSwipeUpVelocity, mMinFlingVelocity); + public void animateCollapse(Runnable startStackCollapse, Runnable after, + PointF collapsePosition) { + ProtoLog.d(WM_SHELL_BUBBLES, "expandedView animate collapse swipeVel=%f minFlingVel=%d" + + " collapsePosition=%f,%f", mSwipeUpVelocity, mMinFlingVelocity, + collapsePosition.x, collapsePosition.y); if (mExpandedView != null) { // Mark it as animating immediately to avoid updates to the view before animation starts mExpandedView.setAnimating(true); @@ -274,6 +277,17 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio } @Override + public boolean shouldAnimateExpansion() { + return false; + } + + @Override + public void animateExpansion(long startDelayMillis, Runnable after, PointF collapsePosition, + PointF bubblePosition) { + // TODO - animate + } + + @Override public void reset() { ProtoLog.d(WM_SHELL_BUBBLES, "reset expandedView collapsed state"); if (mExpandedView == null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 9c0144292c12..98ff0eed9c11 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -866,6 +866,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return; } if (mTransitionDragActive) { + // Do not create an indicator at all if we're not past transition height. + if (ev.getRawY() < mContext.getResources().getDimensionPixelSize(com.android + .wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height) + && mMoveToDesktopAnimator == null) { + return; + } final DesktopModeVisualIndicator.IndicatorType indicatorType = mDesktopTasksController.updateVisualIndicator( relevantDecor.mTaskInfo, diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig index 156be389fe84..f33bcb7f9643 100644 --- a/location/java/android/location/flags/location.aconfig +++ b/location/java/android/location/flags/location.aconfig @@ -11,7 +11,7 @@ flag { name: "location_bypass" namespace: "location" description: "Enable location bypass appops behavior" - bug: "301150056" + bug: "329151785" } flag { diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig index 6cf9c6fa7616..bf3942559b8a 100644 --- a/media/java/android/media/flags/media_better_together.aconfig +++ b/media/java/android/media/flags/media_better_together.aconfig @@ -109,5 +109,5 @@ flag { name: "enable_null_session_in_media_browser_service" namespace: "media_solutions" description: "Enables apps owning a MediaBrowserService to disconnect all connected browsers." - bug: "263520343" + bug: "185136506" } diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java index 76664a66ad53..ddcb25b15ccc 100644 --- a/media/java/android/media/tv/ad/TvAdManager.java +++ b/media/java/android/media/tv/ad/TvAdManager.java @@ -61,7 +61,6 @@ import java.util.concurrent.Executor; @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW) @SystemService(Context.TV_AD_SERVICE) public final class TvAdManager { - // TODO: implement more methods and unhide APIs. private static final String TAG = "TvAdManager"; /** diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java index d20490860448..dd2a534676d8 100644 --- a/media/java/android/media/tv/ad/TvAdView.java +++ b/media/java/android/media/tv/ad/TvAdView.java @@ -393,16 +393,12 @@ public class TvAdView extends ViewGroup { } /** - * Sets a listener to be invoked when an input event is not handled - * by the TV AD service. + * Sets a listener to be invoked when an input event is not handled by the TV AD service. * * @param listener The callback to be invoked when the unhandled input event is received. */ - public void setOnUnhandledInputEventListener( - @NonNull @CallbackExecutor Executor executor, - @NonNull OnUnhandledInputEventListener listener) { + public void setOnUnhandledInputEventListener(@NonNull OnUnhandledInputEventListener listener) { mOnUnhandledInputEventListener = listener; - // TODO: handle CallbackExecutor } /** @@ -441,6 +437,9 @@ public class TvAdView extends ViewGroup { /** * Prepares the AD service of corresponding {@link TvAdService}. * + * <p>This should be called before calling {@link #startAdService()}. Otherwise, + * {@link #startAdService()} is a no-op. + * * @param serviceId the AD service ID, which can be found in TvAdServiceInfo#getId(). */ public void prepareAdService(@NonNull String serviceId, @NonNull String type) { @@ -455,6 +454,9 @@ public class TvAdView extends ViewGroup { /** * Starts the AD service. + * + * <p>This should be called after calling {@link #prepareAdService(String, String)}. Otherwise, + * it's a no-op. */ public void startAdService() { if (DEBUG) { @@ -467,6 +469,8 @@ public class TvAdView extends ViewGroup { /** * Stops the AD service. + * + * <p>It's a no-op if the service is not started. */ public void stopAdService() { if (DEBUG) { diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java index 4500a220523d..5dcfd3b0237d 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java @@ -40,7 +40,7 @@ public class DataBoostWebServiceFlow { * * This can be called using the JavaScript below: * <script type="text/javascript"> - * function getRequestedCapability(duration) { + * function getRequestedCapability() { * DataBoostWebServiceFlow.getRequestedCapability(); * } * </script> @@ -57,6 +57,25 @@ public class DataBoostWebServiceFlow { * * This can be called using the JavaScript below: * <script type="text/javascript"> + * function notifyPurchaseSuccessful(duration_ms_long = 0) { + * DataBoostWebServiceFlow.notifyPurchaseSuccessful(duration_ms_long); + * } + * </script> + * + * @param duration The duration for which the premium capability is purchased in milliseconds. + * NOTE: The duration parameter is not used. + */ + @JavascriptInterface + public void notifyPurchaseSuccessful(long duration) { + mActivity.onPurchaseSuccessful(); + } + + /** + * Interface method allowing the carrier website to notify the slice purchase application of + * a successful premium capability purchase. + * + * This can be called using the JavaScript below: + * <script type="text/javascript"> * function notifyPurchaseSuccessful() { * DataBoostWebServiceFlow.notifyPurchaseSuccessful(); * } diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml index 6019aa8560e1..42d0cc403372 100644 --- a/packages/CompanionDeviceManager/res/values/strings.xml +++ b/packages/CompanionDeviceManager/res/values/strings.xml @@ -52,12 +52,21 @@ <!-- Confirmation for associating an application with a companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] --> <string name="title_app_streaming">Allow <strong><xliff:g id="app_name" example="Exo">%1$s</xliff:g></strong> to access this information from your phone</string> + <!-- Confirmation for associating an application with a companion device of APP_STREAMING profile (type) with mirroring enabled [CHAR LIMIT=NONE] --> + <string name="title_app_streaming_with_mirroring">Allow <strong><xliff:g id="app_name" example="Exo">%1$s</xliff:g></strong> to stream your phone\u2019s apps?</string> + + <!-- Summary for associating an application with a companion device of APP_STREAMING profile [CHAR LIMIT=NONE] --> + <string name="summary_app_streaming">%1$s will have access to anything that’s visible or played on the phone, including audio, photos, passwords, and messages.<br/><br/>%1$s will be able to stream apps until you remove access to this permission.</string> + <!-- Title of the helper dialog for APP_STREAMING profile [CHAR LIMIT=30]. --> <string name="helper_title_app_streaming">Cross-device services</string> <!-- Description of the helper dialog for APP_STREAMING profile. [CHAR LIMIT=NONE] --> <string name="helper_summary_app_streaming"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="display_name" example="Chromebook">%2$s</xliff:g> to stream apps between your devices</string> + <!-- Description of the helper dialog for APP_STREAMING profile with mirroring enabled. [CHAR LIMIT=NONE] --> + <string name="helper_summary_app_streaming_with_mirroring"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="display_name" example="Chromebook">%2$s</xliff:g> to display and stream apps between your devices</string> + <!-- ================= DEVICE_PROFILE_AUTOMOTIVE_PROJECTION ================= --> <!-- Confirmation for associating an application with a companion device of AUTOMOTIVE_PROJECTION profile (type) [CHAR LIMIT=NONE] --> @@ -85,6 +94,12 @@ <!-- Confirmation for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile (type) [CHAR LIMIT=NONE] --> <string name="title_nearby_device_streaming">Allow <strong><xliff:g id="device_name" example="NearbyStreamer">%1$s</xliff:g></strong> to take this action?</string> + <!-- Confirmation for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile (type) with mirroring enabled [CHAR LIMIT=NONE] --> + <string name="title_nearby_device_streaming_with_mirroring">Allow <strong><xliff:g id="device_name" example="NearbyStreamer">%1$s</xliff:g></strong> to stream your phone\u2019s apps and system features?</string> + + <!-- Summary for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile [CHAR LIMIT=NONE] --> + <string name="summary_nearby_device_streaming">%1$s will have access to anything that’s visible or played on your phone, including audio, photos, payment info, passwords, and messages.<br/><br/>%1$s will be able to stream apps and system features until you remove access to this permission.</string> + <!-- Description of the helper dialog for NEARBY_DEVICE_STREAMING profile. [CHAR LIMIT=NONE] --> <string name="helper_summary_nearby_device_streaming"><xliff:g id="app_name" example="NearbyStreamerApp">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_name" example="NearbyDevice">%2$s</xliff:g> to stream apps and other system features to nearby devices</string> diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java index 4c1f6313c3a0..1231b639ead6 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java @@ -179,7 +179,7 @@ public class CompanionDeviceActivity extends FragmentActivity implements // onActivityResult() after the association is created. private @Nullable DeviceFilterPair<?> mSelectedDevice; - private LinearLayoutManager mPermissionsLayoutManager = new LinearLayoutManager(this); + private final LinearLayoutManager mPermissionsLayoutManager = new LinearLayoutManager(this); @Override public void onCreate(Bundle savedInstanceState) { @@ -484,10 +484,18 @@ public class CompanionDeviceActivity extends FragmentActivity implements } title = getHtmlFromResources(this, PROFILE_TITLES.get(deviceProfile), deviceName); + + if (PROFILE_SUMMARIES.containsKey(deviceProfile)) { + final int summaryResourceId = PROFILE_SUMMARIES.get(deviceProfile); + final Spanned summary = getHtmlFromResources(this, summaryResourceId, + deviceName); + mSummary.setText(summary); + } else { + mSummary.setVisibility(View.GONE); + } + setupPermissionList(deviceProfile); - // Summary is not needed for selfManaged dialog. - mSummary.setVisibility(View.GONE); mTitle.setText(title); mVendorHeaderName.setText(vendorName); mVendorHeader.setVisibility(View.VISIBLE); @@ -692,6 +700,11 @@ public class CompanionDeviceActivity extends FragmentActivity implements private void setupPermissionList(String deviceProfile) { final List<Integer> permissionTypes = new ArrayList<>( PROFILE_PERMISSIONS.get(deviceProfile)); + if (permissionTypes.isEmpty()) { + // Nothing to do if there are no permission types. + return; + } + mPermissionListAdapter.setPermissionType(permissionTypes); mPermissionListRecyclerView.setAdapter(mPermissionListAdapter); mPermissionListRecyclerView.setLayoutManager(mPermissionsLayoutManager); diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java index 23a11d618085..dc68bccc8f0a 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java @@ -27,11 +27,13 @@ import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static java.util.Collections.unmodifiableMap; import static java.util.Collections.unmodifiableSet; +import android.companion.virtual.flags.Flags; import android.os.Build; import android.util.ArrayMap; import android.util.ArraySet; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -122,10 +124,19 @@ final class CompanionDeviceResources { static final Map<String, Integer> PROFILE_TITLES; static { final Map<String, Integer> map = new ArrayMap<>(); - map.put(DEVICE_PROFILE_APP_STREAMING, R.string.title_app_streaming); + if (Flags.interactiveScreenMirror()) { + map.put(DEVICE_PROFILE_APP_STREAMING, R.string.title_app_streaming_with_mirroring); + } else { + map.put(DEVICE_PROFILE_APP_STREAMING, R.string.title_app_streaming); + } map.put(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, R.string.title_automotive_projection); map.put(DEVICE_PROFILE_COMPUTER, R.string.title_computer); - map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, R.string.title_nearby_device_streaming); + if (Flags.interactiveScreenMirror()) { + map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, + R.string.title_nearby_device_streaming_with_mirroring); + } else { + map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, R.string.title_nearby_device_streaming); + } map.put(DEVICE_PROFILE_WATCH, R.string.confirmation_title); map.put(DEVICE_PROFILE_GLASSES, R.string.confirmation_title_glasses); map.put(null, R.string.confirmation_title); @@ -138,6 +149,11 @@ final class CompanionDeviceResources { final Map<String, Integer> map = new ArrayMap<>(); map.put(DEVICE_PROFILE_WATCH, R.string.summary_watch); map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses); + if (Flags.interactiveScreenMirror()) { + map.put(DEVICE_PROFILE_APP_STREAMING, R.string.summary_app_streaming); + map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, + R.string.summary_nearby_device_streaming); + } map.put(null, R.string.summary_generic); PROFILE_SUMMARIES = unmodifiableMap(map); @@ -146,11 +162,16 @@ final class CompanionDeviceResources { static final Map<String, List<Integer>> PROFILE_PERMISSIONS; static { final Map<String, List<Integer>> map = new ArrayMap<>(); - map.put(DEVICE_PROFILE_APP_STREAMING, Arrays.asList(PERMISSION_APP_STREAMING)); map.put(DEVICE_PROFILE_COMPUTER, Arrays.asList( PERMISSION_NOTIFICATION_LISTENER_ACCESS, PERMISSION_STORAGE)); - map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, - Arrays.asList(PERMISSION_NEARBY_DEVICE_STREAMING)); + if (Flags.interactiveScreenMirror()) { + map.put(DEVICE_PROFILE_APP_STREAMING, Collections.emptyList()); + map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, Collections.emptyList()); + } else { + map.put(DEVICE_PROFILE_APP_STREAMING, Arrays.asList(PERMISSION_APP_STREAMING)); + map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, + Arrays.asList(PERMISSION_NEARBY_DEVICE_STREAMING)); + } if (Build.VERSION.SDK_INT > UPSIDE_DOWN_CAKE) { map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATIONS, PERMISSION_PHONE, PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_CALENDAR, diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java index 8f32dbb86d04..fe0e021b363c 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java @@ -26,6 +26,7 @@ import static com.android.companiondevicemanager.Utils.getHtmlFromResources; import android.annotation.Nullable; import android.companion.AssociationRequest; +import android.companion.virtual.flags.Flags; import android.content.DialogInterface; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; @@ -129,7 +130,9 @@ public class CompanionVendorHelperDialogFragment extends DialogFragment { case DEVICE_PROFILE_APP_STREAMING: title = getHtmlFromResources(getContext(), R.string.helper_title_app_streaming); summary = getHtmlFromResources( - getContext(), R.string.helper_summary_app_streaming, title, displayName); + getContext(), Flags.interactiveScreenMirror() + ? R.string.helper_summary_app_streaming_with_mirroring + : R.string.helper_summary_app_streaming, title, displayName); break; case DEVICE_PROFILE_COMPUTER: diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java index 5d71b7d98fdc..37b5d408a508 100644 --- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java +++ b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java @@ -38,15 +38,15 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; -import android.util.BackgroundThread; -import android.util.LongArrayQueue; import android.util.Slog; import android.util.Xml; +import android.utils.BackgroundThread; +import android.utils.LongArrayQueue; +import android.utils.XmlUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; -import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java index 9217e7012e7e..0fcec268fe9c 100644 --- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java +++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java @@ -29,7 +29,6 @@ import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; import android.os.Build; import android.os.Environment; -import android.os.FileUtils; import android.os.PowerManager; import android.os.RecoverySystem; import android.os.SystemClock; @@ -42,10 +41,11 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import android.util.Slog; +import android.utils.ArrayUtils; +import android.utils.FileUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; import com.android.server.PackageWatchdog.FailureReasons; import com.android.server.PackageWatchdog.PackageHealthObserver; import com.android.server.PackageWatchdog.PackageHealthObserverImpact; diff --git a/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java new file mode 100644 index 000000000000..fa4d6afc03d3 --- /dev/null +++ b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.utils; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.io.File; +import java.util.List; +import java.util.Objects; + +/** + * Copied over from frameworks/base/core/java/com/android/internal/util/ArrayUtils.java + * + * @hide + */ +public class ArrayUtils { + private ArrayUtils() { /* cannot be instantiated */ } + public static final File[] EMPTY_FILE = new File[0]; + + + /** + * Return first index of {@code value} in {@code array}, or {@code -1} if + * not found. + */ + public static <T> int indexOf(@Nullable T[] array, T value) { + if (array == null) return -1; + for (int i = 0; i < array.length; i++) { + if (Objects.equals(array[i], value)) return i; + } + return -1; + } + + /** @hide */ + public static @NonNull File[] defeatNullable(@Nullable File[] val) { + return (val != null) ? val : EMPTY_FILE; + } + + /** + * Checks if given array is null or has zero elements. + */ + public static boolean isEmpty(@Nullable int[] array) { + return array == null || array.length == 0; + } + + /** + * True if the byte array is null or has length 0. + */ + public static boolean isEmpty(@Nullable byte[] array) { + return array == null || array.length == 0; + } + + /** + * Converts from List of bytes to byte array + * @param list + * @return byte[] + */ + public static byte[] toPrimitive(List<byte[]> list) { + if (list.size() == 0) { + return new byte[0]; + } + int byteLen = list.get(0).length; + byte[] array = new byte[list.size() * byteLen]; + for (int i = 0; i < list.size(); i++) { + for (int j = 0; j < list.get(i).length; j++) { + array[i * byteLen + j] = list.get(i)[j]; + } + } + return array; + } + + /** + * Adds value to given array if not already present, providing set-like + * behavior. + */ + public static @NonNull int[] appendInt(@Nullable int[] cur, int val) { + return appendInt(cur, val, false); + } + + /** + * Adds value to given array. + */ + public static @NonNull int[] appendInt(@Nullable int[] cur, int val, + boolean allowDuplicates) { + if (cur == null) { + return new int[] { val }; + } + final int n = cur.length; + if (!allowDuplicates) { + for (int i = 0; i < n; i++) { + if (cur[i] == val) { + return cur; + } + } + } + int[] ret = new int[n + 1]; + System.arraycopy(cur, 0, ret, 0, n); + ret[n] = val; + return ret; + } +} diff --git a/packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java b/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java index a6ae68f62f10..afcf6895fd0d 100644 --- a/packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java +++ b/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.util; +package android.utils; import android.annotation.NonNull; import android.os.Handler; diff --git a/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java new file mode 100644 index 000000000000..e4923bfc4ecb --- /dev/null +++ b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.utils; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Bits and pieces copied from hidden API of android.os.FileUtils. + * + * @hide + */ +public class FileUtils { + /** + * Read a text file into a String, optionally limiting the length. + * + * @param file to read (will not seek, so things like /proc files are OK) + * @param max length (positive for head, negative of tail, 0 for no limit) + * @param ellipsis to add of the file was truncated (can be null) + * @return the contents of the file, possibly truncated + * @throws IOException if something goes wrong reading the file + * @hide + */ + public static @Nullable String readTextFile(@Nullable File file, @Nullable int max, + @Nullable String ellipsis) throws IOException { + InputStream input = new FileInputStream(file); + // wrapping a BufferedInputStream around it because when reading /proc with unbuffered + // input stream, bytes read not equal to buffer size is not necessarily the correct + // indication for EOF; but it is true for BufferedInputStream due to its implementation. + BufferedInputStream bis = new BufferedInputStream(input); + try { + long size = file.length(); + if (max > 0 || (size > 0 && max == 0)) { // "head" mode: read the first N bytes + if (size > 0 && (max == 0 || size < max)) max = (int) size; + byte[] data = new byte[max + 1]; + int length = bis.read(data); + if (length <= 0) return ""; + if (length <= max) return new String(data, 0, length); + if (ellipsis == null) return new String(data, 0, max); + return new String(data, 0, max) + ellipsis; + } else if (max < 0) { // "tail" mode: keep the last N + int len; + boolean rolled = false; + byte[] last = null; + byte[] data = null; + do { + if (last != null) rolled = true; + byte[] tmp = last; + last = data; + data = tmp; + if (data == null) data = new byte[-max]; + len = bis.read(data); + } while (len == data.length); + + if (last == null && len <= 0) return ""; + if (last == null) return new String(data, 0, len); + if (len > 0) { + rolled = true; + System.arraycopy(last, len, last, 0, last.length - len); + System.arraycopy(data, 0, last, last.length - len, len); + } + if (ellipsis == null || !rolled) return new String(last); + return ellipsis + new String(last); + } else { // "cat" mode: size unknown, read it all in streaming fashion + ByteArrayOutputStream contents = new ByteArrayOutputStream(); + int len; + byte[] data = new byte[1024]; + do { + len = bis.read(data); + if (len > 0) contents.write(data, 0, len); + } while (len == data.length); + return contents.toString(); + } + } finally { + bis.close(); + input.close(); + } + } + + /** + * Perform an fsync on the given FileOutputStream. The stream at this + * point must be flushed but not yet closed. + * + * @hide + */ + public static boolean sync(FileOutputStream stream) { + try { + if (stream != null) { + stream.getFD().sync(); + } + return true; + } catch (IOException e) { + } + return false; + } + + /** + * List the files in the directory or return empty file. + * + * @hide + */ + public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) { + return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles()) + : ArrayUtils.EMPTY_FILE; + } +} diff --git a/packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java b/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java index 948ebcca0263..fdb15e2333d5 100644 --- a/packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java +++ b/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.util; +package android.utils; import android.annotation.NonNull; import android.os.Handler; diff --git a/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java new file mode 100644 index 000000000000..5cdc2536129a --- /dev/null +++ b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.utils; + +import libcore.util.EmptyArray; + +import java.util.NoSuchElementException; + +/** + * Copied from frameworks/base/core/java/android/util/LongArrayQueue.java + * + * @hide + */ +public class LongArrayQueue { + + private long[] mValues; + private int mSize; + private int mHead; + private int mTail; + + private long[] newUnpaddedLongArray(int num) { + return new long[num]; + } + /** + * Initializes a queue with the given starting capacity. + * + * @param initialCapacity the capacity. + */ + public LongArrayQueue(int initialCapacity) { + if (initialCapacity == 0) { + mValues = EmptyArray.LONG; + } else { + mValues = newUnpaddedLongArray(initialCapacity); + } + mSize = 0; + mHead = mTail = 0; + } + + /** + * Initializes a queue with default starting capacity. + */ + public LongArrayQueue() { + this(16); + } + + /** @hide */ + public static int growSize(int currentSize) { + return currentSize <= 4 ? 8 : currentSize * 2; + } + + private void grow() { + if (mSize < mValues.length) { + throw new IllegalStateException("Queue not full yet!"); + } + final int newSize = growSize(mSize); + final long[] newArray = newUnpaddedLongArray(newSize); + final int r = mValues.length - mHead; // Number of elements on and to the right of head. + System.arraycopy(mValues, mHead, newArray, 0, r); + System.arraycopy(mValues, 0, newArray, r, mHead); + mValues = newArray; + mHead = 0; + mTail = mSize; + } + + /** + * Returns the number of elements in the queue. + */ + public int size() { + return mSize; + } + + /** + * Removes all elements from this queue. + */ + public void clear() { + mSize = 0; + mHead = mTail = 0; + } + + /** + * Adds a value to the tail of the queue. + * + * @param value the value to be added. + */ + public void addLast(long value) { + if (mSize == mValues.length) { + grow(); + } + mValues[mTail] = value; + mTail = (mTail + 1) % mValues.length; + mSize++; + } + + /** + * Removes an element from the head of the queue. + * + * @return the element at the head of the queue. + * @throws NoSuchElementException if the queue is empty. + */ + public long removeFirst() { + if (mSize == 0) { + throw new NoSuchElementException("Queue is empty!"); + } + final long ret = mValues[mHead]; + mHead = (mHead + 1) % mValues.length; + mSize--; + return ret; + } + + /** + * Returns the element at the given position from the head of the queue, where 0 represents the + * head of the queue. + * + * @param position the position from the head of the queue. + * @return the element found at the given position. + * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or + * {@code position} >= {@link #size()} + */ + public long get(int position) { + if (position < 0 || position >= mSize) { + throw new IndexOutOfBoundsException("Index " + position + + " not valid for a queue of size " + mSize); + } + final int index = (mHead + position) % mValues.length; + return mValues[index]; + } + + /** + * Returns the element at the head of the queue, without removing it. + * + * @return the element at the head of the queue. + * @throws NoSuchElementException if the queue is empty + */ + public long peekFirst() { + if (mSize == 0) { + throw new NoSuchElementException("Queue is empty!"); + } + return mValues[mHead]; + } + + /** + * Returns the element at the tail of the queue. + * + * @return the element at the tail of the queue. + * @throws NoSuchElementException if the queue is empty. + */ + public long peekLast() { + if (mSize == 0) { + throw new NoSuchElementException("Queue is empty!"); + } + final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1; + return mValues[index]; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + if (mSize <= 0) { + return "{}"; + } + + final StringBuilder buffer = new StringBuilder(mSize * 64); + buffer.append('{'); + buffer.append(get(0)); + for (int i = 1; i < mSize; i++) { + buffer.append(", "); + buffer.append(get(i)); + } + buffer.append('}'); + return buffer.toString(); + } +} diff --git a/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java new file mode 100644 index 000000000000..dbbef61f6777 --- /dev/null +++ b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.utils; + +import android.annotation.NonNull; +import android.system.ErrnoException; +import android.system.Os; + +import com.android.modules.utils.TypedXmlPullParser; + +import libcore.util.XmlObjectFactory; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.BufferedInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Copied over partly from frameworks/base/core/java/com/android/internal/util/XmlUtils.java + * + * @hide + */ +public class XmlUtils { + + private static final String STRING_ARRAY_SEPARATOR = ":"; + + /** @hide */ + public static final void beginDocument(XmlPullParser parser, String firstElementName) + throws XmlPullParserException, IOException { + int type; + while ((type = parser.next()) != parser.START_TAG + && type != parser.END_DOCUMENT) { + // Do nothing + } + + if (type != parser.START_TAG) { + throw new XmlPullParserException("No start tag found"); + } + + if (!parser.getName().equals(firstElementName)) { + throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() + + ", expected " + firstElementName); + } + } + + /** @hide */ + public static boolean nextElementWithin(XmlPullParser parser, int outerDepth) + throws IOException, XmlPullParserException { + for (;;) { + int type = parser.next(); + if (type == XmlPullParser.END_DOCUMENT + || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) { + return false; + } + if (type == XmlPullParser.START_TAG + && parser.getDepth() == outerDepth + 1) { + return true; + } + } + } + + private static XmlPullParser newPullParser() { + try { + XmlPullParser parser = XmlObjectFactory.newXmlPullParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + return parser; + } catch (XmlPullParserException e) { + throw new AssertionError(); + } + } + + /** @hide */ + public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in) + throws IOException { + final byte[] magic = new byte[4]; + if (in instanceof FileInputStream) { + try { + Os.pread(((FileInputStream) in).getFD(), magic, 0, magic.length, 0); + } catch (ErrnoException e) { + throw e.rethrowAsIOException(); + } + } else { + if (!in.markSupported()) { + in = new BufferedInputStream(in); + } + in.mark(8); + in.read(magic); + in.reset(); + } + + final TypedXmlPullParser xml; + xml = (TypedXmlPullParser) newPullParser(); + try { + xml.setInput(in, "UTF_8"); + } catch (XmlPullParserException e) { + throw new IOException(e); + } + return xml; + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt index 4f2fa790f9d7..6ba684d9dfcc 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt @@ -574,7 +574,7 @@ class CredentialAutofillService : AutofillService() { ) { viewNode.autofillId?.let { val domain = viewNode.webDomain - val request = viewNode.credentialManagerRequest + val request = viewNode.pendingCredentialRequest if (domain != null && request != null) { responseClientState.putBoolean( WEBVIEW_REQUESTED_CREDENTIAL_KEY, true) @@ -604,8 +604,8 @@ class CredentialAutofillService : AutofillService() { sessionId: Int ): MutableList<CredentialOption> { val credentialOptions: MutableList<CredentialOption> = mutableListOf() - if (Flags.autofillCredmanDevIntegration() && viewNode.credentialManagerRequest != null) { - viewNode.credentialManagerRequest + if (Flags.autofillCredmanDevIntegration() && viewNode.pendingCredentialRequest != null) { + viewNode.pendingCredentialRequest ?.getCredentialOptions() ?.forEach { credentialOption -> credentialOption.candidateQueryData diff --git a/packages/CredentialManager/wear/res/values/strings.xml b/packages/CredentialManager/wear/res/values/strings.xml index 9480e64aa134..ee8bb786c3f8 100644 --- a/packages/CredentialManager/wear/res/values/strings.xml +++ b/packages/CredentialManager/wear/res/values/strings.xml @@ -21,9 +21,6 @@ <!-- Title of a screen prompting if the user would like to use their saved passkey. [CHAR LIMIT=80] --> <string name="use_passkey_title">Use passkey?</string> - <!-- Title of a screen prompting if the user would like to use their saved passkey. -[CHAR LIMIT=80] --> - <string name="use_sign_in_with_provider_title">Use your sign in for %1$s</string> <!-- Title of a screen prompting if the user would like to sign in with provider [CHAR LIMIT=80] --> <string name="use_password_title">Use password?</string> @@ -35,6 +32,8 @@ <string name="dialog_sign_in_options_button">Sign-in Options</string> <!-- Title for multiple credentials folded screen. [CHAR LIMIT=NONE] --> <string name="sign_in_options_title">Sign-in Options</string> + <!-- Provider settings list title. [CHAR LIMIT=NONE] --> + <string name="provider_list_title">Manage sign-ins</string> <!-- Title for multiple credentials screen. [CHAR LIMIT=NONE] --> <string name="choose_sign_in_title">Choose a sign in</string> <!-- Title for multiple credentials screen with only passkeys. [CHAR LIMIT=NONE] --> diff --git a/packages/CredentialManager/wear/robotests/Android.bp b/packages/CredentialManager/wear/robotests/Android.bp new file mode 100644 index 000000000000..c0a1822a771f --- /dev/null +++ b/packages/CredentialManager/wear/robotests/Android.bp @@ -0,0 +1,28 @@ +package { + // See: http://go/android-license-faq + default_applicable_licenses: ["frameworks_base_license"], +} + +android_robolectric_test { + name: "CredentialSelectorTests", + srcs: [ + "src/**/*.kt", + ], + // Include test libraries. + instrumentation_for: "ClockworkCredentialManager", + libs: [ + "androidx.test.runner", + "androidx.test.ext.junit", + "kotlinx_coroutines_android", + "kotlinx_coroutines", + "kotlinx-coroutines-core", + "kotlinx_coroutines_test", + "mockito-robolectric-prebuilt", + "mockito-kotlin2", + "CredentialManagerShared", + "ClockworkCredentialManager", + "framework_graphics_flags_java_lib", + ], + java_resource_dirs: ["config"], + upstream: true, +} diff --git a/packages/CredentialManager/wear/robotests/config/robolectric.properties b/packages/CredentialManager/wear/robotests/config/robolectric.properties new file mode 100644 index 000000000000..140e42b65c9a --- /dev/null +++ b/packages/CredentialManager/wear/robotests/config/robolectric.properties @@ -0,0 +1,16 @@ +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +sdk=NEWEST_SDK + diff --git a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt new file mode 100644 index 000000000000..3422d3dc4d94 --- /dev/null +++ b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.credentialmanager + +import java.time.Instant +import android.graphics.drawable.Drawable +import com.android.credentialmanager.model.get.CredentialEntryInfo +import com.android.credentialmanager.model.get.ActionEntryInfo +import com.android.credentialmanager.model.get.AuthenticationEntryInfo +import com.android.credentialmanager.model.Request +import androidx.test.filters.SmallTest +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Test +import org.mockito.kotlin.mock +import org.junit.runner.RunWith +import com.android.credentialmanager.model.CredentialType +import com.google.common.truth.Truth.assertThat +import com.android.credentialmanager.ui.mappers.toGet +import com.android.credentialmanager.model.get.ProviderInfo +import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry.PerUserNameEntries + +/** Unit tests for [CredentialSelectorUiStateGetMapper]. */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class CredentialSelectorUiStateGetMapperTest { + + private val mDrawable = mock<Drawable>() + + private val actionEntryInfo = + ActionEntryInfo( + providerId = "", + entryKey = "", + entrySubkey = "", + pendingIntent = null, + fillInIntent = null, + title = "title", + icon = mDrawable, + subTitle = "subtitle", + ) + + private val authenticationEntryInfo = + AuthenticationEntryInfo( + providerId = "", + entryKey = "", + entrySubkey = "", + pendingIntent = null, + fillInIntent = null, + title = "title", + providerDisplayName = "", + icon = mDrawable, + isUnlockedAndEmpty = true, + isLastUnlocked = true + ) + + val passkeyCredentialEntryInfo = + createCredentialEntryInfo(credentialType = CredentialType.PASSKEY, userName = "userName") + + val unknownCredentialEntryInfo = + createCredentialEntryInfo(credentialType = CredentialType.UNKNOWN, userName = "userName2") + + val passwordCredentialEntryInfo = + createCredentialEntryInfo(credentialType = CredentialType.PASSWORD, userName = "userName") + + val recentlyUsedPasskeyCredential = + createCredentialEntryInfo(credentialType = + CredentialType.PASSKEY, lastUsedTimeMillis = 2L, userName = "userName") + + val recentlyUsedPasswordCredential = + createCredentialEntryInfo(credentialType = + CredentialType.PASSWORD, lastUsedTimeMillis = 2L, userName = "userName") + + val credentialList1 = listOf( + passkeyCredentialEntryInfo, + passwordCredentialEntryInfo + ) + + val credentialList2 = listOf( + passkeyCredentialEntryInfo, + passwordCredentialEntryInfo, + recentlyUsedPasskeyCredential, + unknownCredentialEntryInfo, + recentlyUsedPasswordCredential + ) + + @Test + fun `On primary screen, just one account returns SingleEntry`() { + val getCredentialUiState = Request.Get( + token = null, + resultReceiver = null, + finalResponseReceiver = null, + providerInfos = listOf(createProviderInfo(credentialList1))).toGet(isPrimary = true) + + assertThat(getCredentialUiState).isEqualTo( + CredentialSelectorUiState.Get.SingleEntry(passkeyCredentialEntryInfo) + ) // prefer passkey over password for selected credential + } + + @Test + fun `On primary screen, multiple accounts returns SingleEntryPerAccount`() { + val getCredentialUiState = Request.Get( + token = null, + resultReceiver = null, + finalResponseReceiver = null, + providerInfos = listOf(createProviderInfo(listOf(passkeyCredentialEntryInfo, + unknownCredentialEntryInfo)))).toGet(isPrimary = true) + + assertThat(getCredentialUiState).isEqualTo( + CredentialSelectorUiState.Get.SingleEntryPerAccount( + sortedEntries = listOf( + passkeyCredentialEntryInfo, // userName + unknownCredentialEntryInfo // userName2 + ), + authenticationEntryList = listOf(authenticationEntryInfo) + )) // prefer passkey from account 1, then unknown from account 2 + } + + @Test + fun `On secondary screen, a MultipleEntry is returned`() { + val getCredentialUiState = Request.Get( + token = null, + resultReceiver = null, + finalResponseReceiver = null, + providerInfos = listOf(createProviderInfo(credentialList1))).toGet(isPrimary = false) + + assertThat(getCredentialUiState).isEqualTo( + CredentialSelectorUiState.Get.MultipleEntry( + listOf(PerUserNameEntries("userName", listOf( + passkeyCredentialEntryInfo, + passwordCredentialEntryInfo)) + ), + listOf(actionEntryInfo), + listOf(authenticationEntryInfo) + )) + } + + @Test + fun `Returned multiple entry is sorted by credentialType and lastUsedTimeMillis`() { + val getCredentialUiState = Request.Get( + token = null, + resultReceiver = null, + finalResponseReceiver = null, + providerInfos = listOf(createProviderInfo(credentialList1), + createProviderInfo(credentialList2))).toGet(isPrimary = false) + + assertThat(getCredentialUiState).isEqualTo( + CredentialSelectorUiState.Get.MultipleEntry( + listOf( + PerUserNameEntries("userName", + listOf( + recentlyUsedPasskeyCredential, // from provider 2 + passkeyCredentialEntryInfo, // from provider 1 or 2 + passkeyCredentialEntryInfo, // from provider 1 or 2 + recentlyUsedPasswordCredential, // from provider 2 + passwordCredentialEntryInfo, // from provider 1 or 2 + passwordCredentialEntryInfo, // from provider 1 or 2 + )), + PerUserNameEntries("userName2", listOf(unknownCredentialEntryInfo)), + ), + listOf(actionEntryInfo, actionEntryInfo), + listOf(authenticationEntryInfo, authenticationEntryInfo) + ) + ) + } + + fun createCredentialEntryInfo( + userName: String, + credentialType: CredentialType = CredentialType.PASSKEY, + lastUsedTimeMillis: Long = 0L + ): CredentialEntryInfo = + CredentialEntryInfo( + providerId = "", + entryKey = "", + entrySubkey = "", + pendingIntent = null, + fillInIntent = null, + credentialType = credentialType, + rawCredentialType = "", + credentialTypeDisplayName = "", + providerDisplayName = "", + userName = userName, + displayName = "", + icon = mDrawable, + shouldTintIcon = false, + lastUsedTimeMillis = Instant.ofEpochMilli(lastUsedTimeMillis), + isAutoSelectable = true, + entryGroupId = "", + isDefaultIconPreferredAsSingleProvider = false, + affiliatedDomain = "", + ) + + fun createProviderInfo(credentials: List<CredentialEntryInfo> = listOf()): ProviderInfo = + ProviderInfo( + id = "providerInfo", + icon = mDrawable, + displayName = "displayName", + credentialEntryList = credentials, + authenticationEntryList = listOf(authenticationEntryInfo), + remoteEntry = null, + actionEntryList = listOf(actionEntryInfo) + ) +} diff --git a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt new file mode 100644 index 000000000000..b79f34c54f51 --- /dev/null +++ b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.credentialmanager + +import org.mockito.kotlin.whenever +import com.android.credentialmanager.model.EntryInfo +import com.android.credentialmanager.model.Request +import androidx.test.filters.SmallTest +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Test +import org.junit.Before +import java.util.Collections.emptyList +import org.junit.runner.RunWith +import android.content.Intent +import com.android.credentialmanager.client.CredentialManagerClient +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.launchIn +import android.credentials.selection.BaseDialogResult +import com.google.common.truth.Truth.assertThat +import org.mockito.kotlin.doReturn +import kotlinx.coroutines.Job +import org.junit.After +import org.robolectric.shadows.ShadowLooper +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.ExperimentalCoroutinesApi + +/** Unit tests for [CredentialSelectorViewModel]. */ +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class CredentialSelectorViewModelTest { + private val testScope = TestScope(UnconfinedTestDispatcher()) + + private val stateFlow: MutableStateFlow<Request?> = MutableStateFlow(Request.Create(null)) + private val credentialManagerClient = mock<CredentialManagerClient>{ + on { requests } doReturn stateFlow + } + private val mViewModel = CredentialSelectorViewModel(credentialManagerClient) + private lateinit var job: Job + + val testEntryInfo = + EntryInfo( + providerId = "", + entryKey = "", + entrySubkey = "", + pendingIntent = null, + fillInIntent = null, + shouldTerminateUiUponSuccessfulProviderResult = true) + + @Before + fun setUp() { + job = checkNotNull(mViewModel).uiState.launchIn(testScope) + } + + @After + fun teardown() { + job.cancel() + } + + @Test + fun `Setting state to idle when receiving null request`() { + stateFlow.value = null + ShadowLooper.idleMainLooper() + + assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Idle) + } + + @Test + fun `Setting state to cancel when receiving Cancel request`() { + stateFlow.value = Request.Cancel(appName = "appName", token = null) + ShadowLooper.idleMainLooper() + + assertThat(mViewModel.uiState.value) + .isEqualTo(CredentialSelectorUiState.Cancel("appName")) + } + + @Test + fun `Setting state to create when receiving Create request`() { + stateFlow.value = Request.Create(token = null) + ShadowLooper.idleMainLooper() + + assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Create) + } + + @Test + fun `Closing app when receiving Close request`() { + stateFlow.value = Request.Close(token = null) + ShadowLooper.idleMainLooper() + + assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close) + } + + @Test + fun `Updates request`() { + val intent = Intent() + + mViewModel.updateRequest(intent) + + verify(credentialManagerClient).updateRequest(intent) + } + + @Test + fun `Back on a single entry screen closes app`() { + mViewModel.openSecondaryScreen() + stateFlow.value = Request.Get( + token = null, + resultReceiver = null, + finalResponseReceiver = null, + providerInfos = emptyList()) + + mViewModel.back() + ShadowLooper.idleMainLooper() + + assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close) + } + + @Test + fun `Back on a multiple entry screen gets us back to a primary screen`() { + mViewModel.openSecondaryScreen() + stateFlow.value = Request.Get( + token = null, + resultReceiver = null, + finalResponseReceiver = null, + providerInfos = emptyList()) + + mViewModel.back() + ShadowLooper.idleMainLooper() + + assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close) + } + + @Test + fun `Back on create request state closes app`() { + stateFlow.value = Request.Create(token = null) + + mViewModel.back() + ShadowLooper.idleMainLooper() + + assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close) + } + + @Test + fun `Back on close request state closes app`() { + stateFlow.value = Request.Close(token = null) + + mViewModel.back() + ShadowLooper.idleMainLooper() + + assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close) + } + + @Test + fun `Back on cancel request state closes app`() { + stateFlow.value = Request.Cancel(appName = "", token = null) + + mViewModel.back() + ShadowLooper.idleMainLooper() + + assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close) + } + + @Test + fun `Back on idle request state closes app`() { + stateFlow.value = null + + mViewModel.back() + ShadowLooper.idleMainLooper() + + assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close) + } + + @Test + fun `Cancel closes the app`() { + mViewModel.cancel() + ShadowLooper.idleMainLooper() + + verify(credentialManagerClient).sendError(BaseDialogResult.RESULT_CODE_DIALOG_USER_CANCELED) + assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close) + } + + @Test + fun `Send entry selection result closes app and calls client method`() { + whenever(credentialManagerClient.sendEntrySelectionResult( + entryInfo = testEntryInfo, + resultCode = null, + resultData = null, + isAutoSelected = false + )).thenReturn(true) + + mViewModel.sendSelectionResult( + entryInfo = testEntryInfo, + resultCode = null, + resultData = null, + isAutoSelected = false) + ShadowLooper.idleMainLooper() + + verify(credentialManagerClient).sendEntrySelectionResult( + testEntryInfo, null, null, false + ) + assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close) + } + + @Test + fun `Send entry selection result does not close app on false return`() { + whenever(credentialManagerClient.sendEntrySelectionResult( + entryInfo = testEntryInfo, + resultCode = null, + resultData = null, + isAutoSelected = false + )).thenReturn(false) + stateFlow.value = Request.Create(null) + + mViewModel.sendSelectionResult(entryInfo = testEntryInfo, resultCode = null, + resultData = null, isAutoSelected = false) + ShadowLooper.idleMainLooper() + + verify(credentialManagerClient).sendEntrySelectionResult( + entryInfo = testEntryInfo, + resultCode = null, + resultData = null, + isAutoSelected = false + ) + assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Create) + } +} diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt index 66be7ba5e079..9d9776301518 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt @@ -59,8 +59,10 @@ class CredentialSelectorViewModel @Inject constructor( isPrimaryScreen, shouldClose ) { request, isPrimary, shouldClose -> + Log.d(TAG, "Request updated: " + request?.toString() + + " isClose: " + shouldClose.toString() + + " isPrimaryScreen: " + isPrimary.toString()) if (shouldClose) { - Log.d(TAG, "Request finished, closing ") return@combine Close } @@ -139,7 +141,10 @@ sealed class CredentialSelectorUiState { data object Idle : CredentialSelectorUiState() sealed class Get : CredentialSelectorUiState() { data class SingleEntry(val entry: CredentialEntryInfo) : Get() - data class SingleEntryPerAccount(val sortedEntries: List<CredentialEntryInfo>) : Get() + data class SingleEntryPerAccount( + val sortedEntries: List<CredentialEntryInfo>, + val authenticationEntryList: List<AuthenticationEntryInfo>, + ) : Get() data class MultipleEntry( val accounts: List<PerUserNameEntries>, val actionEntryList: List<ActionEntryInfo>, diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt index 405de1d3acc6..bf4c988679b9 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt @@ -29,6 +29,7 @@ import androidx.wear.compose.foundation.rememberSwipeToDismissBoxState import androidx.wear.compose.navigation.rememberSwipeDismissableNavController import androidx.wear.compose.navigation.rememberSwipeDismissableNavHostState import com.android.credentialmanager.CredentialSelectorUiState +import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntryPerAccount import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntry import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry import com.android.credentialmanager.CredentialSelectorViewModel @@ -45,6 +46,8 @@ import com.google.android.horologist.compose.navscaffold.scrollable import com.android.credentialmanager.model.CredentialType import com.android.credentialmanager.model.EntryInfo import com.android.credentialmanager.ui.screens.multiple.MultiCredentialsFoldScreen +import com.android.credentialmanager.ui.screens.multiple.MultiCredentialsFlattenScreen + @OptIn(ExperimentalHorologistApi::class) @Composable @@ -78,59 +81,70 @@ fun WearApp( scrollable(Screen.SinglePasskeyScreen.route) { SinglePasskeyScreen( - credentialSelectorUiState = viewModel.uiState.value as SingleEntry, + entry = (remember { uiState } as SingleEntry).entry, columnState = it.columnState, + flowEngine = flowEngine, ) } scrollable(Screen.SignInWithProviderScreen.route) { SignInWithProviderScreen( - credentialSelectorUiState = viewModel.uiState.value as SingleEntry, + entry = (remember { uiState } as SingleEntry).entry, columnState = it.columnState, + flowEngine = flowEngine, ) } scrollable(Screen.MultipleCredentialsScreenFold.route) { MultiCredentialsFoldScreen( - credentialSelectorUiState = viewModel.uiState.value as MultipleEntry, - screenIcon = null, + credentialSelectorUiState = (remember { uiState } as SingleEntryPerAccount), columnState = it.columnState, + flowEngine = flowEngine, ) } - } - BackHandler(true) { - viewModel.back() - } - Log.d(TAG, "uiState change, state: $uiState") - when (val state = uiState) { - CredentialSelectorUiState.Idle -> { - if (navController.currentDestination?.route != Screen.Loading.route) { - navController.navigateToLoading() - } - } - is CredentialSelectorUiState.Get -> { - handleGetNavigation( - navController = navController, - state = state, - onCloseApp = onCloseApp, - selectEntry = selectEntry + + scrollable(Screen.MultipleCredentialsScreenFlatten.route) { + MultiCredentialsFlattenScreen( + credentialSelectorUiState = (remember { uiState } as MultipleEntry), + columnState = it.columnState, + flowEngine = flowEngine, ) } - - CredentialSelectorUiState.Create -> { - // TODO: b/301206624 - Implement create flow - onCloseApp() + } + BackHandler(true) { + viewModel.back() } + Log.d(TAG, "uiState change, state: $uiState") + when (val state = uiState) { + CredentialSelectorUiState.Idle -> { + if (navController.currentDestination?.route != Screen.Loading.route) { + navController.navigateToLoading() + } + } - is CredentialSelectorUiState.Cancel -> { - onCloseApp() - } + is CredentialSelectorUiState.Get -> { + handleGetNavigation( + navController = navController, + state = state, + onCloseApp = onCloseApp, + selectEntry = selectEntry + ) + } + + CredentialSelectorUiState.Create -> { + // TODO: b/301206624 - Implement create flow + onCloseApp() + } - CredentialSelectorUiState.Close -> { - onCloseApp() + is CredentialSelectorUiState.Cancel -> { + onCloseApp() + } + + CredentialSelectorUiState.Close -> { + onCloseApp() + } } } -} private fun handleGetNavigation( navController: NavController, @@ -157,13 +171,12 @@ private fun handleGetNavigation( } } - is MultipleEntry -> { - navController.navigateToMultipleCredentialsFoldScreen() - } + is SingleEntryPerAccount -> { + navController.navigateToMultipleCredentialsFoldScreen() + } - else -> { - // TODO: b/301206470 - Implement other get flows - onCloseApp() + is MultipleEntry -> { + navController.navigateToMultipleCredentialsFlattenScreen() + } } } -} diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt index 03b0931f3eb4..7a936b603ec1 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt @@ -32,11 +32,14 @@ fun Request.Get.toGet(isPrimary: Boolean): CredentialSelectorUiState.Get { return if (isPrimary) { if (accounts.size == 1) { CredentialSelectorUiState.Get.SingleEntry( - accounts[0].value.minWith(comparator) + entry = accounts[0].value.minWith(comparator) ) } else { CredentialSelectorUiState.Get.SingleEntryPerAccount( - accounts.map { it.value.minWith(comparator) }.sortedWith(comparator) + sortedEntries = accounts.map { + it.value.minWith(comparator) + }.sortedWith(comparator), + authenticationEntryList = providerInfos.flatMap { it.authenticationEntryList } ) } } else { diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt index 11188b436a05..d54103cd66e8 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt @@ -15,112 +15,53 @@ */ package com.android.credentialmanager.ui.screens.multiple -import android.graphics.drawable.Drawable -import com.android.credentialmanager.ui.screens.UiState -import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.navigation.NavHostController -import androidx.navigation.compose.rememberNavController import androidx.wear.compose.material.MaterialTheme import androidx.wear.compose.material.Text import com.android.credentialmanager.ui.components.SignInHeader import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry +import com.android.credentialmanager.FlowEngine import com.android.credentialmanager.R -import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract -import com.android.credentialmanager.model.get.ActionEntryInfo import com.android.credentialmanager.model.get.CredentialEntryInfo import com.android.credentialmanager.ui.components.CredentialsScreenChip import com.google.android.horologist.annotations.ExperimentalHorologistApi import com.google.android.horologist.compose.layout.ScalingLazyColumn import com.google.android.horologist.compose.layout.ScalingLazyColumnState - /** * Screen that shows multiple credentials to select from, grouped by accounts * * @param credentialSelectorUiState The app bar view model. - * @param screenIcon The view model corresponding to the home page. * @param columnState ScalingLazyColumn configuration to be be applied * @param modifier styling for composable - * @param viewModel ViewModel that updates ui state for this screen - * @param navController handles navigation events from this screen + * @param flowEngine [FlowEngine] that updates ui state for this screen */ @OptIn(ExperimentalHorologistApi::class) @Composable fun MultiCredentialsFlattenScreen( credentialSelectorUiState: MultipleEntry, - screenIcon: Drawable?, - columnState: ScalingLazyColumnState, - modifier: Modifier = Modifier, - viewModel: MultiCredentialsFlattenViewModel = hiltViewModel(), - navController: NavHostController = rememberNavController(), -) { - val uiState by viewModel.uiState.collectAsStateWithLifecycle() - - when (val state = uiState) { - UiState.CredentialScreen -> { - MultiCredentialsFlattenScreen( - state = credentialSelectorUiState, - columnState = columnState, - screenIcon = screenIcon, - onActionEntryClicked = viewModel::onActionEntryClicked, - onCredentialClicked = viewModel::onCredentialClicked, - modifier = modifier, - ) - } - - is UiState.CredentialSelected -> { - val launcher = rememberLauncherForActivityResult( - StartBalIntentSenderForResultContract() - ) { - viewModel.onInfoRetrieved(it.resultCode, null) - } - - SideEffect { - state.intentSenderRequest?.let { - launcher.launch(it) - } - } - } - - UiState.Cancel -> { - navController.popBackStack() - } - } -} - -@OptIn(ExperimentalHorologistApi::class) -@Composable -fun MultiCredentialsFlattenScreen( - state: MultipleEntry, columnState: ScalingLazyColumnState, - screenIcon: Drawable?, - onActionEntryClicked: (entryInfo: ActionEntryInfo) -> Unit, - onCredentialClicked: (entryInfo: CredentialEntryInfo) -> Unit, - modifier: Modifier, + flowEngine: FlowEngine, ) { + val selectEntry = flowEngine.getEntrySelector() ScalingLazyColumn( columnState = columnState, - modifier = modifier.fillMaxSize(), + modifier = Modifier.fillMaxSize(), ) { item { // make this credential specific if all credentials are same SignInHeader( - icon = screenIcon, + icon = null, title = stringResource(R.string.sign_in_options_title), ) } - state.accounts.forEach { userNameEntries -> + credentialSelectorUiState.accounts.forEach { userNameEntries -> item { Text( text = userNameEntries.userName, @@ -135,17 +76,16 @@ fun MultiCredentialsFlattenScreen( item { CredentialsScreenChip( label = credential.userName, - onClick = { onCredentialClicked(credential) }, - secondaryLabel = credential.userName, + onClick = { selectEntry(credential, false) }, + secondaryLabel = credential.credentialTypeDisplayName, icon = credential.icon, - modifier = modifier, ) } } } item { Text( - text = "Manage Sign-ins", + text = stringResource(R.string.provider_list_title), modifier = Modifier .padding(top = 6.dp) .padding(horizontal = 10.dp), @@ -153,14 +93,13 @@ fun MultiCredentialsFlattenScreen( ) } - state.actionEntryList.forEach { + credentialSelectorUiState.actionEntryList.forEach {actionEntry -> item { CredentialsScreenChip( - label = it.title, - onClick = { onActionEntryClicked(it) }, + label = actionEntry.title, + onClick = { selectEntry(actionEntry, false) }, secondaryLabel = null, - icon = it.icon, - modifier = modifier, + icon = actionEntry.icon, ) } } diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenViewModel.kt deleted file mode 100644 index ee5f3f4799d6..000000000000 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenViewModel.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.credentialmanager.ui.screens.multiple - -import android.content.Intent -import android.credentials.selection.ProviderPendingIntentResponse -import android.credentials.selection.UserSelectionDialogResult -import androidx.lifecycle.ViewModel -import com.android.credentialmanager.client.CredentialManagerClient -import com.android.credentialmanager.ktx.getIntentSenderRequest -import com.android.credentialmanager.model.Request -import com.android.credentialmanager.model.get.ActionEntryInfo -import com.android.credentialmanager.model.get.CredentialEntryInfo -import com.android.credentialmanager.ui.screens.UiState -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import javax.inject.Inject - -/** ViewModel for [MultiCredentialsFlattenScreen].*/ -@HiltViewModel -class MultiCredentialsFlattenViewModel @Inject constructor( - private val credentialManagerClient: CredentialManagerClient, -) : ViewModel() { - - private lateinit var requestGet: Request.Get - private lateinit var entryInfo: CredentialEntryInfo - - private val _uiState = - MutableStateFlow<UiState>(UiState.CredentialScreen) - val uiState: StateFlow<UiState> = _uiState - - fun onCredentialClicked(entryInfo: CredentialEntryInfo) { - this.entryInfo = entryInfo - _uiState.value = UiState.CredentialSelected( - intentSenderRequest = entryInfo.getIntentSenderRequest() - ) - } - - fun onCancelClicked() { - _uiState.value = UiState.Cancel - } - - fun onInfoRetrieved( - resultCode: Int? = null, - resultData: Intent? = null, - ) { - val userSelectionDialogResult = UserSelectionDialogResult( - requestGet.token, - entryInfo.providerId, - entryInfo.entryKey, - entryInfo.entrySubkey, - if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null - ) - credentialManagerClient.sendResult(userSelectionDialogResult) - } - - fun onActionEntryClicked(actionEntryInfo: ActionEntryInfo) { - // TODO(b/322797032)to be filled out - } -}
\ No newline at end of file diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt index 5515c8696b87..6f32c9906a1d 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt @@ -16,24 +16,15 @@ package com.android.credentialmanager.ui.screens.multiple -import com.android.credentialmanager.ui.screens.UiState -import android.graphics.drawable.Drawable -import androidx.activity.compose.rememberLauncherForActivityResult import com.android.credentialmanager.R import androidx.compose.ui.res.stringResource import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.navigation.NavHostController -import androidx.navigation.compose.rememberNavController import com.android.credentialmanager.CredentialSelectorUiState -import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract +import com.android.credentialmanager.FlowEngine import com.android.credentialmanager.model.get.CredentialEntryInfo import com.android.credentialmanager.ui.components.DismissChip import com.android.credentialmanager.ui.components.CredentialsScreenChip @@ -49,74 +40,22 @@ import com.android.credentialmanager.model.CredentialType * Screen that shows multiple credentials to select from. * * @param credentialSelectorUiState The app bar view model. - * @param screenIcon The view model corresponding to the home page. * @param columnState ScalingLazyColumn configuration to be be applied - * @param modifier styling for composable - * @param viewModel ViewModel that updates ui state for this screen - * @param navController handles navigation events from this screen */ @OptIn(ExperimentalHorologistApi::class) @Composable fun MultiCredentialsFoldScreen( - credentialSelectorUiState: CredentialSelectorUiState.Get.MultipleEntry, - screenIcon: Drawable?, + credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntryPerAccount, columnState: ScalingLazyColumnState, - modifier: Modifier = Modifier, - viewModel: MultiCredentialsFoldViewModel = hiltViewModel(), - navController: NavHostController = rememberNavController(), -) { - val uiState by viewModel.uiState.collectAsStateWithLifecycle() - - when (val state = uiState) { - UiState.CredentialScreen -> { - MultiCredentialsFoldScreen( - state = credentialSelectorUiState, - onSignInOptionsClicked = viewModel::onSignInOptionsClicked, - onCredentialClicked = viewModel::onCredentialClicked, - onCancelClicked = viewModel::onCancelClicked, - screenIcon = screenIcon, - columnState = columnState, - modifier = modifier - ) - } - - is UiState.CredentialSelected -> { - val launcher = rememberLauncherForActivityResult( - StartBalIntentSenderForResultContract() - ) { - viewModel.onInfoRetrieved(it.resultCode, null) - } - - SideEffect { - state.intentSenderRequest?.let { - launcher.launch(it) - } - } - } - - UiState.Cancel -> { - navController.popBackStack() - } - } -} - -@OptIn(ExperimentalHorologistApi::class) -@Composable -fun MultiCredentialsFoldScreen( - state: CredentialSelectorUiState.Get.MultipleEntry, - onSignInOptionsClicked: () -> Unit, - onCredentialClicked: (entryInfo: CredentialEntryInfo) -> Unit, - onCancelClicked: () -> Unit, - screenIcon: Drawable?, - columnState: ScalingLazyColumnState, - modifier: Modifier, + flowEngine: FlowEngine, ) { + val selectEntry = flowEngine.getEntrySelector() ScalingLazyColumn( columnState = columnState, - modifier = modifier.fillMaxSize(), + modifier = Modifier.fillMaxSize(), ) { // flatten all credentials into one - val credentials = state.accounts.flatMap { it.sortedCredentialEntryList } + val credentials = credentialSelectorUiState.sortedEntries item { var title = stringResource(R.string.choose_sign_in_title) if (credentials.all{ it.credentialType == CredentialType.PASSKEY }) { @@ -126,7 +65,7 @@ fun MultiCredentialsFoldScreen( } SignInHeader( - icon = screenIcon, + icon = null, title = title, modifier = Modifier .padding(top = 6.dp), @@ -137,22 +76,24 @@ fun MultiCredentialsFoldScreen( item { CredentialsScreenChip( label = credential.userName, - onClick = { onCredentialClicked(credential) }, + onClick = { selectEntry(credential, false) }, secondaryLabel = credential.credentialTypeDisplayName, icon = credential.icon, ) } } - state.authenticationEntryList.forEach { authenticationEntryInfo -> + credentialSelectorUiState.authenticationEntryList.forEach { authenticationEntryInfo -> item { LockedProviderChip(authenticationEntryInfo) { - // TODO(b/322797032) invoke LockedProviderScreen here using flow engine - } + selectEntry(authenticationEntryInfo, false) } } } - - item { SignInOptionsChip(onSignInOptionsClicked)} - item { DismissChip(onCancelClicked) } + item { + SignInOptionsChip { flowEngine.openSecondaryScreen() } + } + item { + DismissChip { flowEngine.cancel() } + } } } diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldViewModel.kt deleted file mode 100644 index 627a63de0934..000000000000 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldViewModel.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.credentialmanager.ui.screens.multiple - -import android.content.Intent -import android.credentials.selection.ProviderPendingIntentResponse -import android.credentials.selection.UserSelectionDialogResult -import androidx.lifecycle.ViewModel -import com.android.credentialmanager.client.CredentialManagerClient -import com.android.credentialmanager.ktx.getIntentSenderRequest -import com.android.credentialmanager.model.Request -import com.android.credentialmanager.model.get.CredentialEntryInfo -import com.android.credentialmanager.ui.screens.UiState -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import javax.inject.Inject - -/** ViewModel for [MultiCredentialsFoldScreen].*/ -@HiltViewModel -class MultiCredentialsFoldViewModel @Inject constructor( - private val credentialManagerClient: CredentialManagerClient, -) : ViewModel() { - - private lateinit var requestGet: Request.Get - private lateinit var entryInfo: CredentialEntryInfo - - private val _uiState = - MutableStateFlow<UiState>(UiState.CredentialScreen) - val uiState: StateFlow<UiState> = _uiState - - fun onCredentialClicked(entryInfo: CredentialEntryInfo) { - this.entryInfo = entryInfo - _uiState.value = UiState.CredentialSelected( - intentSenderRequest = entryInfo.getIntentSenderRequest() - ) - } - - fun onSignInOptionsClicked() { - // TODO(b/322797032) Implement navigation route for single credential screen to multiple - // credentials - } - - fun onCancelClicked() { - _uiState.value = UiState.Cancel - } - - fun onInfoRetrieved( - resultCode: Int? = null, - resultData: Intent? = null, - ) { - val userSelectionDialogResult = UserSelectionDialogResult( - requestGet.token, - entryInfo.providerId, - entryInfo.entryKey, - entryInfo.entrySubkey, - if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null - ) - credentialManagerClient.sendResult(userSelectionDialogResult) - } -} diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt index b2595a1e500c..e79176bdebcf 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt @@ -19,122 +19,62 @@ package com.android.credentialmanager.ui.screens.single.passkey import androidx.compose.foundation.layout.Column -import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.navigation.NavHostController -import androidx.navigation.compose.rememberNavController -import com.android.credentialmanager.CredentialSelectorUiState +import com.android.credentialmanager.FlowEngine import com.android.credentialmanager.model.get.CredentialEntryInfo import com.android.credentialmanager.R -import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract import com.android.credentialmanager.ui.components.AccountRow import com.android.credentialmanager.ui.components.ContinueChip import com.android.credentialmanager.ui.components.DismissChip import com.android.credentialmanager.ui.components.SignInHeader import com.android.credentialmanager.ui.components.SignInOptionsChip import com.android.credentialmanager.ui.screens.single.SingleAccountScreen -import com.android.credentialmanager.ui.screens.UiState import com.google.android.horologist.annotations.ExperimentalHorologistApi import com.google.android.horologist.compose.layout.ScalingLazyColumnState /** * Screen that shows sign in with provider credential. * - * @param credentialSelectorUiState The app bar view model. + * @param entry The password entry * @param columnState ScalingLazyColumn configuration to be be applied to SingleAccountScreen * @param modifier styling for composable - * @param viewModel ViewModel that updates ui state for this screen - * @param navController handles navigation events from this screen + * @param flowEngine [FlowEngine] that updates ui state for this screen */ @OptIn(ExperimentalHorologistApi::class) @Composable fun SinglePasskeyScreen( - credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntry, - columnState: ScalingLazyColumnState, - modifier: Modifier = Modifier, - viewModel: SinglePasskeyScreenViewModel = hiltViewModel(), - navController: NavHostController = rememberNavController(), -) { - viewModel.initialize(credentialSelectorUiState.entry) - - val uiState by viewModel.uiState.collectAsStateWithLifecycle() - - when (val state = uiState) { - UiState.CredentialScreen -> { - SinglePasskeyScreen( - credentialSelectorUiState.entry, - columnState, - modifier, - viewModel - ) - } - - is UiState.CredentialSelected -> { - val launcher = rememberLauncherForActivityResult( - StartBalIntentSenderForResultContract() - ) { - viewModel.onPasskeyInfoRetrieved(it.resultCode, null) - } - - SideEffect { - state.intentSenderRequest?.let { - launcher.launch(it) - } - } - } - - UiState.Cancel -> { - // TODO(b/322797032) add valid navigation path here for going back - navController.popBackStack() - } - } -} - -@OptIn(ExperimentalHorologistApi::class) -@Composable -fun SinglePasskeyScreen( entry: CredentialEntryInfo, columnState: ScalingLazyColumnState, modifier: Modifier = Modifier, - viewModel: SinglePasskeyScreenViewModel, + flowEngine: FlowEngine, ) { SingleAccountScreen( headerContent = { SignInHeader( icon = entry.icon, - title = stringResource(R.string.use_passkey_title), + title = stringResource(R.string.use_password_title), ) }, accountContent = { - if (entry.displayName != null) { - AccountRow( + AccountRow( primaryText = checkNotNull(entry.displayName), secondaryText = entry.userName, modifier = Modifier.padding(top = 10.dp), ) - } else { - AccountRow( - primaryText = entry.userName, - modifier = Modifier.padding(top = 10.dp), - ) - } }, columnState = columnState, modifier = modifier.padding(horizontal = 10.dp) ) { item { + val selectEntry = flowEngine.getEntrySelector() Column { - ContinueChip(viewModel::onContinueClick) - SignInOptionsChip(viewModel::onSignInOptionsClick) - DismissChip(viewModel::onDismissClick) + ContinueChip { selectEntry(entry, false) } + SignInOptionsChip{ flowEngine.openSecondaryScreen() } + DismissChip { flowEngine.cancel() } } } } diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt deleted file mode 100644 index 37ffaca3cf45..000000000000 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.credentialmanager.ui.screens.single.passkey - -import android.content.Intent -import android.credentials.selection.UserSelectionDialogResult -import android.credentials.selection.ProviderPendingIntentResponse -import androidx.annotation.MainThread -import androidx.lifecycle.ViewModel -import com.android.credentialmanager.ktx.getIntentSenderRequest -import com.android.credentialmanager.model.Request -import com.android.credentialmanager.client.CredentialManagerClient -import com.android.credentialmanager.model.get.CredentialEntryInfo -import dagger.hilt.android.lifecycle.HiltViewModel -import com.android.credentialmanager.ui.screens.UiState -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import javax.inject.Inject - -@HiltViewModel -class SinglePasskeyScreenViewModel @Inject constructor( - private val credentialManagerClient: CredentialManagerClient, -) : ViewModel() { - - private val _uiState = - MutableStateFlow<UiState>(UiState.CredentialScreen) - val uiState: StateFlow<UiState> = _uiState - - private lateinit var requestGet: Request.Get - private lateinit var entryInfo: CredentialEntryInfo - - - @MainThread - fun initialize(entry: CredentialEntryInfo) { - this.entryInfo = entry - } - - fun onDismissClick() { - _uiState.value = UiState.Cancel - } - - fun onContinueClick() { - _uiState.value = UiState.CredentialSelected( - intentSenderRequest = entryInfo.getIntentSenderRequest() - ) - } - - fun onSignInOptionsClick() { - } - - fun onPasskeyInfoRetrieved( - resultCode: Int? = null, - resultData: Intent? = null, - ) { - val userSelectionDialogResult = UserSelectionDialogResult( - requestGet.token, - entryInfo.providerId, - entryInfo.entryKey, - entryInfo.entrySubkey, - if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null - ) - credentialManagerClient.sendResult(userSelectionDialogResult) - } -} - diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt index b0ece0d0453a..3a86feb4203b 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt @@ -16,100 +16,43 @@ package com.android.credentialmanager.ui.screens.single.signInWithProvider -import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.navigation.NavHostController -import androidx.navigation.compose.rememberNavController -import com.android.credentialmanager.CredentialSelectorUiState +import com.android.credentialmanager.FlowEngine import com.android.credentialmanager.model.get.CredentialEntryInfo -import com.android.credentialmanager.R -import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract import com.android.credentialmanager.ui.components.AccountRow import com.android.credentialmanager.ui.components.ContinueChip import com.android.credentialmanager.ui.components.DismissChip import com.android.credentialmanager.ui.components.SignInHeader import com.android.credentialmanager.ui.components.SignInOptionsChip import com.android.credentialmanager.ui.screens.single.SingleAccountScreen -import com.android.credentialmanager.ui.screens.UiState import com.google.android.horologist.annotations.ExperimentalHorologistApi import com.google.android.horologist.compose.layout.ScalingLazyColumnState /** * Screen that shows sign in with provider credential. * - * @param credentialSelectorUiState The app bar view model. + * @param entry The password entry. * @param columnState ScalingLazyColumn configuration to be be applied to SingleAccountScreen * @param modifier styling for composable - * @param viewModel ViewModel that updates ui state for this screen - * @param navController handles navigation events from this screen + * @param flowEngine [FlowEngine] that updates ui state for this screen */ @OptIn(ExperimentalHorologistApi::class) @Composable fun SignInWithProviderScreen( - credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntry, - columnState: ScalingLazyColumnState, - modifier: Modifier = Modifier, - viewModel: SignInWithProviderViewModel = hiltViewModel(), - navController: NavHostController = rememberNavController(), -) { - viewModel.initialize(credentialSelectorUiState.entry) - - val uiState by viewModel.uiState.collectAsStateWithLifecycle() - - when (uiState) { - UiState.CredentialScreen -> { - SignInWithProviderScreen( - credentialSelectorUiState.entry, - columnState, - modifier, - viewModel - ) - } - - is UiState.CredentialSelected -> { - val launcher = rememberLauncherForActivityResult( - StartBalIntentSenderForResultContract() - ) { - viewModel.onInfoRetrieved(it.resultCode, null) - } - - SideEffect { - (uiState as UiState.CredentialSelected).intentSenderRequest?.let { - launcher.launch(it) - } - } - } - - UiState.Cancel -> { - // TODO(b/322797032) add valid navigation path here for going back - navController.popBackStack() - } - } -} - -@OptIn(ExperimentalHorologistApi::class) -@Composable -fun SignInWithProviderScreen( entry: CredentialEntryInfo, columnState: ScalingLazyColumnState, modifier: Modifier = Modifier, - viewModel: SignInWithProviderViewModel, + flowEngine: FlowEngine, ) { SingleAccountScreen( headerContent = { SignInHeader( icon = entry.icon, - title = stringResource(R.string.use_sign_in_with_provider_title, - entry.providerDisplayName), + title = entry.providerDisplayName, ) }, accountContent = { @@ -130,12 +73,13 @@ fun SignInWithProviderScreen( columnState = columnState, modifier = modifier.padding(horizontal = 10.dp) ) { - item { - Column { - ContinueChip(viewModel::onContinueClick) - SignInOptionsChip(viewModel::onSignInOptionsClick) - DismissChip(viewModel::onDismissClick) - } - } + item { + val selectEntry = flowEngine.getEntrySelector() + Column { + ContinueChip { selectEntry(entry, false) } + SignInOptionsChip{ flowEngine.openSecondaryScreen() } + DismissChip { flowEngine.cancel() } + } + } } }
\ No newline at end of file diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderViewModel.kt deleted file mode 100644 index 7ba45e5012e8..000000000000 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderViewModel.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.credentialmanager.ui.screens.single.signInWithProvider - -import android.content.Intent -import android.credentials.selection.ProviderPendingIntentResponse -import android.credentials.selection.UserSelectionDialogResult -import androidx.annotation.MainThread -import androidx.lifecycle.ViewModel -import com.android.credentialmanager.ktx.getIntentSenderRequest -import com.android.credentialmanager.model.Request -import com.android.credentialmanager.client.CredentialManagerClient -import com.android.credentialmanager.model.get.CredentialEntryInfo -import dagger.hilt.android.lifecycle.HiltViewModel -import com.android.credentialmanager.ui.screens.UiState -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import javax.inject.Inject - -/** ViewModel for [SignInWithProviderScreen].*/ -@HiltViewModel -class SignInWithProviderViewModel @Inject constructor( - private val credentialManagerClient: CredentialManagerClient, -) : ViewModel() { - - private val _uiState = - MutableStateFlow<UiState>(UiState.CredentialScreen) - val uiState: StateFlow<UiState> = _uiState - - private lateinit var requestGet: Request.Get - private lateinit var entryInfo: CredentialEntryInfo - - @MainThread - fun initialize(entry: CredentialEntryInfo) { - this.entryInfo = entry - } - - fun onDismissClick() { - _uiState.value = UiState.Cancel - } - - fun onContinueClick() { - _uiState.value = UiState.CredentialSelected( - intentSenderRequest = entryInfo.getIntentSenderRequest() - ) - } - - fun onSignInOptionsClick() { - // TODO(b/322797032) Implement navigation route for single credential screen to multiple - // credentials - } - - fun onInfoRetrieved( - resultCode: Int? = null, - resultData: Intent? = null, - ) { - val userSelectionDialogResult = UserSelectionDialogResult( - requestGet.token, - entryInfo.providerId, - entryInfo.entryKey, - entryInfo.entrySubkey, - if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null - ) - credentialManagerClient.sendResult(userSelectionDialogResult) - } -} - diff --git a/packages/EasterEgg/res/values-night/styles.xml b/packages/EasterEgg/res/values-night/styles.xml index 4edf6926da41..6ea2eaee821c 100644 --- a/packages/EasterEgg/res/values-night/styles.xml +++ b/packages/EasterEgg/res/values-night/styles.xml @@ -15,7 +15,7 @@ --> <resources> <style name="AppTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen"> - <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> + <item name="android:windowLayoutInDisplayCutoutMode">always</item> <item name="android:windowLightNavigationBar">false</item> </style> </resources> diff --git a/packages/EasterEgg/res/values/styles.xml b/packages/EasterEgg/res/values/styles.xml index e576526f49b7..4a2cb48f60e5 100644 --- a/packages/EasterEgg/res/values/styles.xml +++ b/packages/EasterEgg/res/values/styles.xml @@ -16,7 +16,7 @@ <resources> <style name="AppTheme" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar.Fullscreen"> - <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> + <item name="android:windowLayoutInDisplayCutoutMode">always</item> <item name="android:windowLightNavigationBar">true</item> </style> diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java index 840c9364de32..b7108c98c3fe 100644 --- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java +++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java @@ -236,7 +236,8 @@ public class MobileMappings { // Handle specific carrier config values for the default data SIM int defaultDataSubId = SubscriptionManager.from(context) .getDefaultDataSubscriptionId(); - PersistableBundle b = configMgr.getConfigForSubId(defaultDataSubId); + PersistableBundle b = configMgr == null ? null + : configMgr.getConfigForSubId(defaultDataSubId); if (b != null) { config.alwaysShowDataRatIcon = b.getBoolean( CarrierConfigManager.KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 2a8eb9bc0845..3266c12f3ad2 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -707,23 +707,36 @@ final class SettingsState { // Update/add new keys for (String key : keyValues.keySet()) { String value = keyValues.get(key); + + // Rename key if it's an aconfig flag. + String flagName = key; + if (Flags.stageAllAconfigFlags() && isConfigSettingsKey(mKey)) { + int slashIndex = flagName.indexOf("/"); + boolean stageFlag = slashIndex > 0 && slashIndex != flagName.length(); + boolean isAconfig = trunkFlagMap != null && trunkFlagMap.containsKey(flagName); + if (stageFlag && isAconfig) { + String flagWithoutNamespace = flagName.substring(slashIndex + 1); + flagName = "staged/" + namespace + "*" + flagWithoutNamespace; + } + } + String oldValue = null; - Setting state = mSettings.get(key); + Setting state = mSettings.get(flagName); if (state == null) { - state = new Setting(key, value, false, packageName, null); - mSettings.put(key, state); - changedKeys.add(key); // key was added + state = new Setting(flagName, value, false, packageName, null); + mSettings.put(flagName, state); + changedKeys.add(flagName); // key was added } else if (state.value != value) { oldValue = state.value; state.update(value, false, packageName, null, true, /* overrideableByRestore */ false); - changedKeys.add(key); // key was updated + changedKeys.add(flagName); // key was updated } else { // this key/value already exists, no change and no logging necessary continue; } - FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, key, value, state.value, + FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, flagName, value, state.value, oldValue, /* tag */ null, /* make default */ false, getUserIdFromKey(mKey), FrameworkStatsLog.SETTING_CHANGED__REASON__UPDATED); addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, state); diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java index 33362a22ffba..e0e31d785ae8 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java @@ -740,6 +740,51 @@ public class SettingsStateTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_STAGE_ALL_ACONFIG_FLAGS) + public void testSetSettingsLockedStagesAconfigFlags() throws Exception { + int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0); + + SettingsState settingsState = new SettingsState( + InstrumentationRegistry.getContext(), mLock, mSettingsFile, configKey, + SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper()); + + String prefix = "test_namespace"; + String packageName = "com.android.flags"; + Map<String, String> keyValues = + Map.of("test_namespace/com.android.flags.flag3", "true"); + + parsed_flags flags = parsed_flags + .newBuilder() + .addParsedFlag(parsed_flag + .newBuilder() + .setPackage(packageName) + .setName("flag3") + .setNamespace(prefix) + .setDescription("test flag") + .addBug("12345678") + .setState(Aconfig.flag_state.DISABLED) + .setPermission(Aconfig.flag_permission.READ_WRITE)) + .build(); + + synchronized (mLock) { + settingsState.loadAconfigDefaultValues( + flags.toByteArray(), settingsState.getAconfigDefaultValues()); + List<String> updates = + settingsState.setSettingsLocked("test_namespace/", keyValues, packageName); + assertEquals(1, updates.size()); + assertEquals(updates.get(0), "staged/test_namespace*com.android.flags.flag3"); + + SettingsState.Setting s; + + s = settingsState.getSettingLocked("test_namespace/com.android.flags.flag3"); + assertNull(s.getValue()); + + s = settingsState.getSettingLocked("staged/test_namespace*com.android.flags.flag3"); + assertEquals("true", s.getValue()); + } + } + + @Test public void testsetSettingsLockedKeepTrunkDefault() throws Exception { final PrintStream os = new PrintStream(new FileOutputStream(mSettingsFile)); os.print( diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index da06830357e8..85bdb295cbb2 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -617,3 +617,14 @@ flag { description: "enables new focus outline for qs tiles when focused on with physical keyboard" bug: "312899524" } + +flag { + name: "edgeback_gesture_handler_get_running_tasks_background" + namespace: "systemui" + description: "Decide whether to get the running tasks from activity manager in EdgebackGestureHandler" + " class on the background thread." + bug: "325041960" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index a3372e3e83da..d0c498475d0b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -24,7 +24,6 @@ import com.android.compose.animation.scene.transitions import com.android.compose.animation.scene.updateSceneTransitionLayoutState import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.communal.shared.model.CommunalScenes -import com.android.systemui.communal.ui.compose.extensions.allowGestures import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.res.R @@ -84,7 +83,7 @@ fun CommunalContainer( SceneTransitionLayout( state = sceneTransitionLayoutState, - modifier = modifier.fillMaxSize().allowGestures(allowed = touchesAllowed), + modifier = modifier.fillMaxSize(), swipeSourceDetector = FixedSizeEdgeDetector( dimensionResource(id = R.dimen.communal_gesture_initiation_width) @@ -93,9 +92,14 @@ fun CommunalContainer( scene( CommunalScenes.Blank, userActions = - mapOf( - Swipe(SwipeDirection.Left, fromSource = Edge.Right) to CommunalScenes.Communal - ) + if (touchesAllowed) { + mapOf( + Swipe(SwipeDirection.Left, fromSource = Edge.Right) to + CommunalScenes.Communal + ) + } else { + emptyMap() + } ) { // This scene shows nothing only allowing for transitions to the communal scene. Box(modifier = Modifier.fillMaxSize()) @@ -104,7 +108,13 @@ fun CommunalContainer( scene( CommunalScenes.Communal, userActions = - mapOf(Swipe(SwipeDirection.Right, fromSource = Edge.Left) to CommunalScenes.Blank), + if (touchesAllowed) { + mapOf( + Swipe(SwipeDirection.Right, fromSource = Edge.Left) to CommunalScenes.Blank + ) + } else { + emptyMap() + }, ) { CommunalScene(viewModel, dialogFactory, modifier = modifier) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 54237bbe498b..0d6b71066557 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -119,7 +119,7 @@ import com.android.systemui.communal.ui.compose.Dimensions.CardOutlineWidth import com.android.systemui.communal.ui.compose.extensions.allowGestures import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset -import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming +import com.android.systemui.communal.ui.compose.extensions.observeTaps import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel @@ -168,7 +168,7 @@ fun CommunalHub( .pointerInput(gridState, contentOffset, contentListState) { // If not in edit mode, don't allow selecting items. if (!viewModel.isEditMode) return@pointerInput - observeTapsWithoutConsuming { offset -> + observeTaps { offset -> val adjustedOffset = offset - contentOffset val index = firstIndexAtOffset(gridState, adjustedOffset) val key = index?.let { keyAtIndexIfEditable(contentListState.list, index) } @@ -277,13 +277,26 @@ fun CommunalHub( if (viewModel is CommunalViewModel && dialogFactory != null) { val isEnableWidgetDialogShowing by viewModel.isEnableWidgetDialogShowing.collectAsState(false) + val isEnableWorkProfileDialogShowing by + viewModel.isEnableWorkProfileDialogShowing.collectAsState(false) EnableWidgetDialog( isEnableWidgetDialogVisible = isEnableWidgetDialogShowing, dialogFactory = dialogFactory, + title = stringResource(id = R.string.dialog_title_to_allow_any_widget), + positiveButtonText = stringResource(id = R.string.button_text_to_open_settings), onConfirm = viewModel::onEnableWidgetDialogConfirm, onCancel = viewModel::onEnableWidgetDialogCancel ) + + EnableWidgetDialog( + isEnableWidgetDialogVisible = isEnableWorkProfileDialogShowing, + dialogFactory = dialogFactory, + title = stringResource(id = R.string.work_mode_off_title), + positiveButtonText = stringResource(id = R.string.work_mode_turn_on), + onConfirm = viewModel::onEnableWorkProfileDialogConfirm, + onCancel = viewModel::onEnableWorkProfileDialogCancel + ) } // This spacer covers the edge of the LazyHorizontalGrid and prevents it from receiving @@ -769,7 +782,16 @@ private fun WidgetContent( modifier: Modifier = Modifier, ) { Box( - modifier = modifier, + modifier = + modifier.thenIf(!viewModel.isEditMode && model.inQuietMode) { + Modifier.pointerInput(Unit) { + // consume tap to prevent the child view from triggering interactions with the + // app widget + observeTaps(shouldConsume = true) { _ -> + viewModel.onOpenEnableWorkProfileDialog() + } + } + } ) { AndroidView( modifier = Modifier.fillMaxSize().allowGestures(allowed = !viewModel.isEditMode), diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/EnableWidgetDialog.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/EnableWidgetDialog.kt index 976a01d85037..df11206826b8 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/EnableWidgetDialog.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/EnableWidgetDialog.kt @@ -16,52 +16,64 @@ package com.android.systemui.communal.ui.compose -import android.app.Dialog -import android.content.DialogInterface +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.res.R +import com.android.systemui.statusbar.phone.ComponentSystemUIDialog import com.android.systemui.statusbar.phone.SystemUIDialogFactory +import com.android.systemui.statusbar.phone.create -/** - * Dialog shown upon tapping a disabled widget. It enables users to navigate to settings and modify - * allowed widget categories. - */ +/** Dialog shown upon tapping a disabled widget which allows users to enable the widget. */ @Composable fun EnableWidgetDialog( isEnableWidgetDialogVisible: Boolean, dialogFactory: SystemUIDialogFactory, + title: String, + positiveButtonText: String, onConfirm: () -> Unit, onCancel: () -> Unit ) { - var dialog: Dialog? by remember { mutableStateOf(null) } + var dialog: ComponentSystemUIDialog? by remember { mutableStateOf(null) } val context = LocalView.current.context DisposableEffect(isEnableWidgetDialogVisible) { if (isEnableWidgetDialogVisible) { dialog = - dialogFactory.create(context = context).apply { - setTitle(context.getString(R.string.dialog_title_to_allow_any_widget)) - setButton( - DialogInterface.BUTTON_NEGATIVE, - context.getString(R.string.cancel) - ) { _, _ -> - onCancel() - } - setButton( - DialogInterface.BUTTON_POSITIVE, - context.getString(R.string.button_text_to_open_settings) - ) { _, _ -> - onConfirm() - } - setCancelable(false) - show() + dialogFactory.create( + context = context, + ) { + DialogComposable(title, positiveButtonText, onConfirm, onCancel) } + dialog?.apply { + setCancelable(true) + setCanceledOnTouchOutside(true) + setOnCancelListener { onCancel() } + show() + } } onDispose { @@ -70,3 +82,62 @@ fun EnableWidgetDialog( } } } + +@Composable +private fun DialogComposable( + title: String, + positiveButtonText: String, + onConfirm: () -> Unit, + onCancel: () -> Unit, +) { + Box( + Modifier.fillMaxWidth() + .padding(top = 18.dp, bottom = 8.dp) + .background(LocalAndroidColorScheme.current.surfaceBright, RoundedCornerShape(28.dp)) + ) { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(20.dp), + ) { + Box( + modifier = Modifier.padding(horizontal = 24.dp).fillMaxWidth().wrapContentHeight(), + contentAlignment = Alignment.TopStart + ) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + color = LocalAndroidColorScheme.current.onSurface, + textAlign = TextAlign.Center, + maxLines = 1, + ) + } + + Box( + modifier = Modifier.padding(end = 12.dp).fillMaxWidth().wrapContentHeight(), + contentAlignment = Alignment.Center + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + ) { + TextButton( + contentPadding = PaddingValues(16.dp), + onClick = onCancel, + ) { + Text( + text = stringResource(R.string.cancel), + ) + } + TextButton( + contentPadding = PaddingValues(16.dp), + onClick = onConfirm, + ) { + Text( + text = positiveButtonText, + ) + } + } + } + } + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt index bc1e429e57cf..379c1656a3ac 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt @@ -30,16 +30,18 @@ import androidx.compose.ui.util.fastForEach import kotlinx.coroutines.coroutineScope /** - * Observe taps without actually consuming them, so child elements can still respond to them. Long + * Observe taps without consuming them by default, so child elements can still respond to them. Long * presses are excluded. */ -suspend fun PointerInputScope.observeTapsWithoutConsuming( +suspend fun PointerInputScope.observeTaps( pass: PointerEventPass = PointerEventPass.Initial, + shouldConsume: Boolean = false, onTap: ((Offset) -> Unit)? = null, ) = coroutineScope { if (onTap == null) return@coroutineScope awaitEachGesture { - awaitFirstDown(pass = pass) + val down = awaitFirstDown(pass = pass) + if (shouldConsume) down.consume() val tapTimeout = viewConfiguration.longPressTimeoutMillis val up = withTimeoutOrNull(tapTimeout) { waitForUpOrCancellation(pass = pass) } if (up != null) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt index b5499b7bce35..bc4e55505579 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt @@ -18,13 +18,16 @@ package com.android.systemui.keyguard.ui.composable import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalView import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.animation.scene.transitions +import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel import javax.inject.Inject @@ -40,6 +43,7 @@ class LockscreenContent constructor( private val viewModel: LockscreenContentViewModel, private val blueprints: Set<@JvmSuppressWildcards ComposableLockscreenSceneBlueprint>, + private val clockInteractor: KeyguardClockInteractor, ) { private val sceneKeyByBlueprint: Map<ComposableLockscreenSceneBlueprint, SceneKey> by lazy { @@ -55,6 +59,12 @@ constructor( ) { val coroutineScope = rememberCoroutineScope() val blueprintId by viewModel.blueprintId(coroutineScope).collectAsState() + val view = LocalView.current + DisposableEffect(view) { + clockInteractor.clockEventController.registerListeners(view) + + onDispose { clockInteractor.clockEventController.unregisterListeners() } + } // Switch smoothly between blueprints, any composable tagged with element() will be // transition-animated between any two blueprints, if they both display the same element. diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt new file mode 100644 index 000000000000..c5ff859def17 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.composable.blueprint + +import androidx.compose.animation.core.tween +import com.android.compose.animation.scene.ElementKey +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.TransitionBuilder +import com.android.compose.animation.scene.transitions +import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.largeClockElementKey +import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smallClockElementKey +import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smartspaceElementKey +import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.ClockFaceInTransition.Companion.CLOCK_IN_MILLIS +import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.ClockFaceInTransition.Companion.CLOCK_IN_START_DELAY_MILLIS +import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.ClockFaceOutTransition.Companion.CLOCK_OUT_MILLIS +import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.SmartspaceMoveTransition.Companion.STATUS_AREA_MOVE_DOWN_MILLIS +import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.SmartspaceMoveTransition.Companion.STATUS_AREA_MOVE_UP_MILLIS + +object ClockTransition { + val defaultClockTransitions = transitions { + from(ClockScenes.smallClockScene, to = ClockScenes.largeClockScene) { + transitioningToLargeClock() + } + from(ClockScenes.largeClockScene, to = ClockScenes.smallClockScene) { + transitioningToSmallClock() + } + } + + private fun TransitionBuilder.transitioningToLargeClock() { + spec = tween(durationMillis = STATUS_AREA_MOVE_UP_MILLIS.toInt()) + timestampRange( + startMillis = CLOCK_IN_START_DELAY_MILLIS.toInt(), + endMillis = (CLOCK_IN_START_DELAY_MILLIS + CLOCK_IN_MILLIS).toInt() + ) { + fade(largeClockElementKey) + } + + timestampRange(endMillis = CLOCK_OUT_MILLIS.toInt()) { fade(smallClockElementKey) } + anchoredTranslate(smallClockElementKey, smartspaceElementKey) + } + + private fun TransitionBuilder.transitioningToSmallClock() { + spec = tween(durationMillis = STATUS_AREA_MOVE_DOWN_MILLIS.toInt()) + timestampRange( + startMillis = CLOCK_IN_START_DELAY_MILLIS.toInt(), + endMillis = (CLOCK_IN_START_DELAY_MILLIS + CLOCK_IN_MILLIS).toInt() + ) { + fade(smallClockElementKey) + } + + timestampRange(endMillis = CLOCK_OUT_MILLIS.toInt()) { fade(largeClockElementKey) } + anchoredTranslate(smallClockElementKey, smartspaceElementKey) + } +} + +object ClockScenes { + val smallClockScene = SceneKey("small-clock-scene") + val largeClockScene = SceneKey("large-clock-scene") +} + +object ClockElementKeys { + val largeClockElementKey = ElementKey("large-clock") + val smallClockElementKey = ElementKey("small-clock") + val smartspaceElementKey = ElementKey("smart-space") +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt index d23cd0c06aab..9509fd22534a 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt @@ -17,19 +17,14 @@ package com.android.systemui.keyguard.ui.composable.blueprint import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.IntRect import com.android.compose.animation.scene.SceneScope -import com.android.compose.modifiers.padding -import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.composable.LockscreenLongPress import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection @@ -38,11 +33,8 @@ import com.android.systemui.keyguard.ui.composable.section.LockSection import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection import com.android.systemui.keyguard.ui.composable.section.NotificationSection import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection -import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection import com.android.systemui.keyguard.ui.composable.section.StatusBarSection import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel -import com.android.systemui.media.controls.ui.composable.MediaCarousel -import com.android.systemui.res.R import dagger.Binds import dagger.Module import dagger.multibindings.IntoSet @@ -59,14 +51,12 @@ constructor( private val viewModel: LockscreenContentViewModel, private val statusBarSection: StatusBarSection, private val clockSection: DefaultClockSection, - private val smartSpaceSection: SmartSpaceSection, private val notificationSection: NotificationSection, private val lockSection: LockSection, private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>, private val bottomAreaSection: BottomAreaSection, private val settingsMenuSection: SettingsMenuSection, private val mediaCarouselSection: MediaCarouselSection, - private val clockInteractor: KeyguardClockInteractor, ) : ComposableLockscreenSceneBlueprint { override val id: String = "default" @@ -74,7 +64,6 @@ constructor( @Composable override fun SceneScope.Content(modifier: Modifier) { val isUdfpsVisible = viewModel.isUdfpsVisible - val burnIn = rememberBurnIn(clockInteractor) val resources = LocalContext.current.resources LockscreenLongPress( @@ -88,40 +77,7 @@ constructor( modifier = Modifier.fillMaxWidth(), ) { with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) } - with(clockSection) { - SmallClock( - burnInParams = burnIn.parameters, - onTopChanged = burnIn.onSmallClockTopChanged, - modifier = Modifier.fillMaxWidth(), - ) - } - with(smartSpaceSection) { - SmartSpace( - burnInParams = burnIn.parameters, - onTopChanged = burnIn.onSmartspaceTopChanged, - modifier = - Modifier.fillMaxWidth() - .padding( - top = { viewModel.getSmartSpacePaddingTop(resources) }, - ) - .padding( - bottom = - dimensionResource( - R.dimen.keyguard_status_view_bottom_margin - ), - ), - ) - } - - if (viewModel.isLargeClockVisible) { - Spacer(modifier = Modifier.weight(weight = 1f)) - with(clockSection) { - LargeClock( - modifier = Modifier.fillMaxWidth(), - ) - } - } - + with(clockSection) { DefaultClockLayout() } with(mediaCarouselSection) { MediaCarousel() } if (viewModel.areNotificationsVisible(resources = resources)) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt index c422c4b58b55..9abfa4233a15 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt @@ -17,19 +17,14 @@ package com.android.systemui.keyguard.ui.composable.blueprint import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.IntRect import com.android.compose.animation.scene.SceneScope -import com.android.compose.modifiers.padding -import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.composable.LockscreenLongPress import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection @@ -38,10 +33,8 @@ import com.android.systemui.keyguard.ui.composable.section.LockSection import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection import com.android.systemui.keyguard.ui.composable.section.NotificationSection import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection -import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection import com.android.systemui.keyguard.ui.composable.section.StatusBarSection import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel -import com.android.systemui.res.R import dagger.Binds import dagger.Module import dagger.multibindings.IntoSet @@ -58,14 +51,12 @@ constructor( private val viewModel: LockscreenContentViewModel, private val statusBarSection: StatusBarSection, private val clockSection: DefaultClockSection, - private val smartSpaceSection: SmartSpaceSection, private val notificationSection: NotificationSection, private val lockSection: LockSection, private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>, private val bottomAreaSection: BottomAreaSection, private val settingsMenuSection: SettingsMenuSection, private val mediaCarouselSection: MediaCarouselSection, - private val clockInteractor: KeyguardClockInteractor, ) : ComposableLockscreenSceneBlueprint { override val id: String = "shortcuts-besides-udfps" @@ -73,7 +64,6 @@ constructor( @Composable override fun SceneScope.Content(modifier: Modifier) { val isUdfpsVisible = viewModel.isUdfpsVisible - val burnIn = rememberBurnIn(clockInteractor) val resources = LocalContext.current.resources LockscreenLongPress( @@ -87,36 +77,7 @@ constructor( modifier = Modifier.fillMaxWidth(), ) { with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) } - with(clockSection) { - SmallClock( - onTopChanged = burnIn.onSmallClockTopChanged, - modifier = Modifier.fillMaxWidth(), - burnInParams = burnIn.parameters, - ) - } - with(smartSpaceSection) { - SmartSpace( - burnInParams = burnIn.parameters, - onTopChanged = burnIn.onSmartspaceTopChanged, - modifier = - Modifier.fillMaxWidth() - .padding( - top = { viewModel.getSmartSpacePaddingTop(resources) } - ) - .padding( - bottom = - dimensionResource( - R.dimen.keyguard_status_view_bottom_margin - ) - ), - ) - } - - if (viewModel.isLargeClockVisible) { - Spacer(modifier = Modifier.weight(weight = 1f)) - with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) } - } - + with(clockSection) { DefaultClockLayout() } with(mediaCarouselSection) { MediaCarousel() } if (viewModel.areNotificationsVisible(resources = resources)) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt index d2184252102b..652412d13aba 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt @@ -18,7 +18,6 @@ package com.android.systemui.keyguard.ui.composable.blueprint import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -27,7 +26,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntRect @@ -35,7 +33,6 @@ import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.padding import com.android.systemui.Flags -import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.composable.LockscreenLongPress import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection @@ -44,7 +41,6 @@ import com.android.systemui.keyguard.ui.composable.section.LockSection import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection import com.android.systemui.keyguard.ui.composable.section.NotificationSection import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection -import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection import com.android.systemui.keyguard.ui.composable.section.StatusBarSection import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel import com.android.systemui.res.R @@ -65,14 +61,12 @@ constructor( private val viewModel: LockscreenContentViewModel, private val statusBarSection: StatusBarSection, private val clockSection: DefaultClockSection, - private val smartSpaceSection: SmartSpaceSection, private val notificationSection: NotificationSection, private val lockSection: LockSection, private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>, private val bottomAreaSection: BottomAreaSection, private val settingsMenuSection: SettingsMenuSection, private val mediaCarouselSection: MediaCarouselSection, - private val clockInteractor: KeyguardClockInteractor, private val largeScreenHeaderHelper: LargeScreenHeaderHelper, ) : ComposableLockscreenSceneBlueprint { @@ -81,8 +75,6 @@ constructor( @Composable override fun SceneScope.Content(modifier: Modifier) { val isUdfpsVisible = viewModel.isUdfpsVisible - val burnIn = rememberBurnIn(clockInteractor) - val resources = LocalContext.current.resources LockscreenLongPress( viewModel = viewModel.longPress, @@ -102,41 +94,7 @@ constructor( modifier = Modifier.fillMaxHeight().weight(weight = 1f), horizontalAlignment = Alignment.CenterHorizontally, ) { - with(clockSection) { - SmallClock( - burnInParams = burnIn.parameters, - onTopChanged = burnIn.onSmallClockTopChanged, - modifier = Modifier.fillMaxWidth(), - ) - } - - with(smartSpaceSection) { - SmartSpace( - burnInParams = burnIn.parameters, - onTopChanged = burnIn.onSmartspaceTopChanged, - modifier = - Modifier.fillMaxWidth() - .padding( - top = { - viewModel.getSmartSpacePaddingTop(resources) - }, - ) - .padding( - bottom = - dimensionResource( - R.dimen - .keyguard_status_view_bottom_margin - ) - ), - ) - } - - if (viewModel.isLargeClockVisible) { - Spacer(modifier = Modifier.weight(weight = 1f)) - with(clockSection) { LargeClock() } - Spacer(modifier = Modifier.weight(weight = 1f)) - } - + with(clockSection) { DefaultClockLayout() } with(mediaCarouselSection) { MediaCarousel() } } with(notificationSection) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt index 152cc67f6c9e..1ab2bc76c1fd 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt @@ -18,27 +18,37 @@ package com.android.systemui.keyguard.ui.composable.section import android.view.ViewGroup import android.widget.FrameLayout +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.viewinterop.AndroidView -import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.modifiers.padding -import com.android.keyguard.KeyguardClockSwitch import com.android.systemui.customization.R as customizationR import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor +import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.largeClockElementKey +import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smallClockElementKey +import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smartspaceElementKey +import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene +import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.smallClockScene +import com.android.systemui.keyguard.ui.composable.blueprint.ClockTransition.defaultClockTransitions +import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn import com.android.systemui.keyguard.ui.composable.modifier.burnInAware import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChanged import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel +import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel +import com.android.systemui.res.R import javax.inject.Inject /** Provides small clock and large clock composables for the default clock face. */ @@ -48,112 +58,152 @@ constructor( private val viewModel: KeyguardClockViewModel, private val clockInteractor: KeyguardClockInteractor, private val aodBurnInViewModel: AodBurnInViewModel, + private val lockscreenContentViewModel: LockscreenContentViewModel, + private val smartSpaceSection: SmartSpaceSection, ) { - @Composable - fun SceneScope.SmallClock( - burnInParams: BurnInParameters, - onTopChanged: (top: Float?) -> Unit, + fun DefaultClockLayout( modifier: Modifier = Modifier, ) { - val clockSize by viewModel.clockSize.collectAsState() - val currentClock by viewModel.currentClock.collectAsState() - viewModel.clock = currentClock + val isLargeClockVisible by viewModel.isLargeClockVisible.collectAsState() + val burnIn = rememberBurnIn(clockInteractor) + val currentScene = + if (isLargeClockVisible) { + largeClockScene + } else { + smallClockScene + } - if (clockSize != KeyguardClockSwitch.SMALL || currentClock?.smallClock?.view == null) { - onTopChanged(null) - return + LaunchedEffect(isLargeClockVisible) { + if (isLargeClockVisible) { + burnIn.onSmallClockTopChanged(null) + } } - val view = LocalView.current - - DisposableEffect(view) { - clockInteractor.clockEventController.registerListeners(view) + SceneTransitionLayout( + modifier = modifier, + currentScene = currentScene, + onChangeScene = {}, + transitions = defaultClockTransitions, + ) { + scene(smallClockScene) { + Column { + SmallClock( + burnInParams = burnIn.parameters, + onTopChanged = burnIn.onSmallClockTopChanged, + modifier = Modifier.element(smallClockElementKey).fillMaxWidth() + ) + SmartSpaceContent() + } + } - onDispose { clockInteractor.clockEventController.unregisterListeners() } + scene(largeClockScene) { + Column { + SmartSpaceContent() + LargeClock(modifier = Modifier.element(largeClockElementKey).fillMaxWidth()) + } + } } + } - MovableElement( - key = ClockElementKey, - modifier = modifier, - ) { + @Composable + private fun SceneScope.SmartSpaceContent( + modifier: Modifier = Modifier, + ) { + val burnIn = rememberBurnIn(clockInteractor) + val resources = LocalContext.current.resources + + MovableElement(key = smartspaceElementKey, modifier = modifier) { content { - AndroidView( - factory = { context -> - FrameLayout(context).apply { - val newClockView = checkNotNull(currentClock).smallClock.view - (newClockView.parent as? ViewGroup)?.removeView(newClockView) - addView(newClockView) - } - }, - modifier = - Modifier.padding( - horizontal = - dimensionResource(customizationR.dimen.clock_padding_start) - ) - .padding(top = { viewModel.getSmallClockTopMargin(view.context) }) - .onTopPlacementChanged(onTopChanged) - .burnInAware( - viewModel = aodBurnInViewModel, - params = burnInParams, - ), - update = { - val newClockView = checkNotNull(currentClock).smallClock.view - it.removeAllViews() - (newClockView.parent as? ViewGroup)?.removeView(newClockView) - it.addView(newClockView) - }, - ) + with(smartSpaceSection) { + this@SmartSpaceContent.SmartSpace( + burnInParams = burnIn.parameters, + onTopChanged = burnIn.onSmartspaceTopChanged, + modifier = + Modifier.fillMaxWidth() + .padding( + top = { + lockscreenContentViewModel.getSmartSpacePaddingTop( + resources + ) + }, + bottom = { + resources.getDimensionPixelSize( + R.dimen.keyguard_status_view_bottom_margin + ) + } + ), + ) + } } } } @Composable - fun SceneScope.LargeClock(modifier: Modifier = Modifier) { - val clockSize by viewModel.clockSize.collectAsState() + private fun SceneScope.SmallClock( + burnInParams: BurnInParameters, + onTopChanged: (top: Float?) -> Unit, + modifier: Modifier = Modifier, + ) { val currentClock by viewModel.currentClock.collectAsState() - viewModel.clock = currentClock - - if (clockSize != KeyguardClockSwitch.LARGE) { + if (currentClock?.smallClock?.view == null) { return } + viewModel.clock = currentClock + val context = LocalContext.current + + AndroidView( + factory = { context -> + FrameLayout(context).apply { + val newClockView = checkNotNull(currentClock).smallClock.view + (newClockView.parent as? ViewGroup)?.removeView(newClockView) + addView(newClockView) + } + }, + update = { + val newClockView = checkNotNull(currentClock).smallClock.view + it.removeAllViews() + (newClockView.parent as? ViewGroup)?.removeView(newClockView) + it.addView(newClockView) + }, + modifier = + modifier + .padding( + horizontal = dimensionResource(customizationR.dimen.clock_padding_start) + ) + .padding(top = { viewModel.getSmallClockTopMargin(context) }) + .onTopPlacementChanged(onTopChanged) + .burnInAware( + viewModel = aodBurnInViewModel, + params = burnInParams, + ), + ) + } + + @Composable + private fun SceneScope.LargeClock(modifier: Modifier = Modifier) { + val currentClock by viewModel.currentClock.collectAsState() + viewModel.clock = currentClock if (currentClock?.largeClock?.view == null) { return } - val view = LocalView.current - - DisposableEffect(view) { - clockInteractor.clockEventController.registerListeners(view) - - onDispose { clockInteractor.clockEventController.unregisterListeners() } - } - - MovableElement( - key = ClockElementKey, - modifier = modifier, - ) { - content { - AndroidView( - factory = { context -> - FrameLayout(context).apply { - val newClockView = checkNotNull(currentClock).largeClock.view - (newClockView.parent as? ViewGroup)?.removeView(newClockView) - addView(newClockView) - } - }, - update = { - val newClockView = checkNotNull(currentClock).largeClock.view - it.removeAllViews() - (newClockView.parent as? ViewGroup)?.removeView(newClockView) - it.addView(newClockView) - }, - modifier = Modifier.fillMaxSize() - ) - } - } + AndroidView( + factory = { context -> + FrameLayout(context).apply { + val newClockView = checkNotNull(currentClock).largeClock.view + (newClockView.parent as? ViewGroup)?.removeView(newClockView) + addView(newClockView) + } + }, + update = { + val newClockView = checkNotNull(currentClock).largeClock.view + it.removeAllViews() + (newClockView.parent as? ViewGroup)?.removeView(newClockView) + it.addView(newClockView) + }, + modifier = modifier.fillMaxSize() + ) } } - -private val ClockElementKey = ElementKey("Clock") diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt index 9b718444b75c..d1cc53e093a5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt @@ -33,7 +33,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView -import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.SceneScope import com.android.systemui.keyguard.KeyguardUnlockAnimationController import com.android.systemui.keyguard.ui.composable.modifier.burnInAware @@ -60,7 +59,7 @@ constructor( modifier: Modifier = Modifier, ) { Column( - modifier = modifier.element(SmartSpaceElementKey).onTopPlacementChanged(onTopChanged), + modifier = modifier.onTopPlacementChanged(onTopChanged), ) { if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) { return @@ -192,5 +191,3 @@ constructor( ) } } - -private val SmartSpaceElementKey = ElementKey("SmartSpace") diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index 0b9f503eec95..51464d059890 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -73,6 +73,7 @@ import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.ComposableScene +import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel import com.android.systemui.statusbar.phone.StatusBarIconController import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager @@ -152,27 +153,29 @@ private fun SceneScope.ShadeScene( mediaHost: MediaHost, modifier: Modifier = Modifier, ) { - val isSplitShade by viewModel.isSplitShade.collectAsState() - if (isSplitShade) { - SplitShade( - viewModel = viewModel, - createTintedIconManager = createTintedIconManager, - createBatteryMeterViewController = createBatteryMeterViewController, - statusBarIconController = statusBarIconController, - mediaCarouselController = mediaCarouselController, - mediaHost = mediaHost, - modifier = modifier, - ) - } else { - SingleShade( - viewModel = viewModel, - createTintedIconManager = createTintedIconManager, - createBatteryMeterViewController = createBatteryMeterViewController, - statusBarIconController = statusBarIconController, - mediaCarouselController = mediaCarouselController, - mediaHost = mediaHost, - modifier = modifier, - ) + val shadeMode by viewModel.shadeMode.collectAsState() + when (shadeMode) { + is ShadeMode.Single -> + SingleShade( + viewModel = viewModel, + createTintedIconManager = createTintedIconManager, + createBatteryMeterViewController = createBatteryMeterViewController, + statusBarIconController = statusBarIconController, + mediaCarouselController = mediaCarouselController, + mediaHost = mediaHost, + modifier = modifier, + ) + is ShadeMode.Split -> + SplitShade( + viewModel = viewModel, + createTintedIconManager = createTintedIconManager, + createBatteryMeterViewController = createBatteryMeterViewController, + statusBarIconController = statusBarIconController, + mediaCarouselController = mediaCarouselController, + mediaHost = mediaHost, + modifier = modifier, + ) + is ShadeMode.Dual -> error("Dual shade is not yet implemented!") } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt index b9b472f5ac6d..6cff30cf0369 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt @@ -21,6 +21,7 @@ import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.SpringSpec import kotlin.math.absoluteValue import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job import kotlinx.coroutines.launch /** @@ -147,13 +148,16 @@ private fun CoroutineScope.animate( } // Animate the progress to its target value. - launch { animatable.animateTo(targetProgress, animationSpec) } - .invokeOnCompletion { - // Settle the state to Idle(target). Note that this will do nothing if this transition - // was replaced/interrupted by another one, and this also runs if this coroutine is - // cancelled, i.e. if [this] coroutine scope is cancelled. - layoutState.finishTransition(transition, target) - } + transition.job = + launch { animatable.animateTo(targetProgress, animationSpec) } + .apply { + invokeOnCompletion { + // Settle the state to Idle(target). Note that this will do nothing if this + // transition was replaced/interrupted by another one, and this also runs if + // this coroutine is cancelled, i.e. if [this] coroutine scope is cancelled. + layoutState.finishTransition(transition, target) + } + } return transition } @@ -174,8 +178,13 @@ private class OneOffTransition( */ lateinit var animatable: Animatable<Float, AnimationVector1D> + /** The job that is animating [animatable]. */ + lateinit var job: Job + override val progress: Float get() = animatable.value + + override fun finish(): Job = job } // TODO(b/290184746): Compute a good default visibility threshold that depends on the layout size diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index b94e49bb0edc..6114499a2f5e 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -20,6 +20,7 @@ package com.android.compose.animation.scene import android.util.Log import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.SpringSpec import androidx.compose.foundation.gestures.Orientation import androidx.compose.runtime.getValue @@ -96,9 +97,15 @@ internal class DraggableHandlerImpl( return false } + val swipeTransition = dragController.swipeTransition + + // Don't intercept a transition that is finishing. + if (swipeTransition.isFinishing) { + return false + } + // Only intercept the current transition if one of the 2 swipes results is also a transition // between the same pair of scenes. - val swipeTransition = dragController.swipeTransition val fromScene = swipeTransition._currentScene val swipes = computeSwipes(fromScene, startedPosition, pointersDown = 1) val (upOrLeft, downOrRight) = swipes.computeSwipesResults(fromScene) @@ -149,15 +156,24 @@ internal class DraggableHandlerImpl( val fromScene = layoutImpl.scene(transitionState.currentScene) val swipes = computeSwipes(fromScene, startedPosition, pointersDown) - val result = swipes.findUserActionResult(fromScene, overSlop, true) - - // As we were unable to locate a valid target scene, the initial SwipeTransition cannot be - // defined. Consequently, a simple NoOp Controller will be returned. - if (result == null) return NoOpDragController + val result = + swipes.findUserActionResult(fromScene, overSlop, true) + // As we were unable to locate a valid target scene, the initial SwipeTransition + // cannot be defined. Consequently, a simple NoOp Controller will be returned. + ?: return NoOpDragController return updateDragController( swipes = swipes, - swipeTransition = SwipeTransition(fromScene, result, swipes, layoutImpl, orientation) + swipeTransition = + SwipeTransition( + layoutImpl.state, + coroutineScope, + fromScene, + result, + swipes, + layoutImpl, + orientation, + ) ) } @@ -277,7 +293,7 @@ private class DragControllerImpl( * @return the consumed delta */ override fun onDrag(delta: Float) { - if (delta == 0f || !isDrivingTransition) return + if (delta == 0f || !isDrivingTransition || swipeTransition.isFinishing) return swipeTransition.dragOffset += delta val (fromScene, acceleratedOffset) = @@ -305,6 +321,8 @@ private class DragControllerImpl( ) { val swipeTransition = SwipeTransition( + layoutState = layoutState, + coroutineScope = draggableHandler.coroutineScope, fromScene = fromScene, result = result, swipes = swipes, @@ -355,15 +373,9 @@ private class DragControllerImpl( } } - private fun snapToScene(scene: SceneKey) { - if (!isDrivingTransition) return - swipeTransition.cancelOffsetAnimation() - layoutState.finishTransition(swipeTransition, idleScene = scene) - } - override fun onStop(velocity: Float, canChangeScene: Boolean) { // The state was changed since the drag started; don't do anything. - if (!isDrivingTransition) { + if (!isDrivingTransition || swipeTransition.isFinishing) { return } @@ -389,7 +401,7 @@ private class DragControllerImpl( coroutineScope = draggableHandler.coroutineScope, initialVelocity = velocity, targetOffset = targetOffset, - onAnimationCompleted = { snapToScene(targetScene.key) } + targetScene = targetScene.key, ) } @@ -451,12 +463,14 @@ private class DragControllerImpl( val result = swipes.findUserActionResultStrict(velocity) if (result == null) { // We will not animate - snapToScene(fromScene.key) + swipeTransition.snapToScene(fromScene.key) return } val newSwipeTransition = SwipeTransition( + layoutState = layoutState, + coroutineScope = draggableHandler.coroutineScope, fromScene = fromScene, result = result, swipes = swipes, @@ -514,6 +528,8 @@ private class DragControllerImpl( } private fun SwipeTransition( + layoutState: BaseSceneTransitionLayoutState, + coroutineScope: CoroutineScope, fromScene: Scene, result: UserActionResult, swipes: Swipes, @@ -530,6 +546,8 @@ private fun SwipeTransition( } return SwipeTransition( + layoutState = layoutState, + coroutineScope = coroutineScope, key = result.transitionKey, _fromScene = fromScene, _toScene = layoutImpl.scene(result.toScene), @@ -541,6 +559,8 @@ private fun SwipeTransition( private fun SwipeTransition(old: SwipeTransition): SwipeTransition { return SwipeTransition( + layoutState = old.layoutState, + coroutineScope = old.coroutineScope, key = old.key, _fromScene = old._fromScene, _toScene = old._toScene, @@ -555,6 +575,8 @@ private fun SwipeTransition(old: SwipeTransition): SwipeTransition { } private class SwipeTransition( + val layoutState: BaseSceneTransitionLayoutState, + val coroutineScope: CoroutineScope, val key: TransitionKey?, val _fromScene: Scene, val _toScene: Scene, @@ -573,7 +595,7 @@ private class SwipeTransition( // Important: If we are going to return early because distance is equal to 0, we should // still make sure we read the offset before returning so that the calling code still // subscribes to the offset value. - val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset + val offset = offsetAnimation?.animatable?.value ?: dragOffset val distance = distance() if (distance == DistanceUnspecified) { @@ -588,20 +610,11 @@ private class SwipeTransition( /** The current offset caused by the drag gesture. */ var dragOffset by mutableFloatStateOf(0f) - /** - * Whether the offset is animated (the user lifted their finger) or if it is driven by gesture. - */ - var isAnimatingOffset by mutableStateOf(false) + /** The offset animation that animates the offset once the user lifts their finger. */ + private var offsetAnimation: OffsetAnimation? by mutableStateOf(null) - // If we are not animating offset, it means the offset is being driven by the user's finger. override val isUserInputOngoing: Boolean - get() = !isAnimatingOffset - - /** The animatable used to animate the offset once the user lifted its finger. */ - val offsetAnimatable = Animatable(0f, OffsetVisibilityThreshold) - - /** Job to check that there is at most one offset animation in progress. */ - private var offsetAnimationJob: Job? = null + get() = offsetAnimation == null /** * The [TransformationSpecImpl] associated to this transition. @@ -617,6 +630,10 @@ private class SwipeTransition( private var lastDistance = DistanceUnspecified + /** Whether [TransitionState.Transition.finish] was called on this transition. */ + var isFinishing = false + private set + /** * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is above * or to the left of [toScene]. @@ -647,25 +664,21 @@ private class SwipeTransition( return distance } - /** Ends any previous [offsetAnimationJob] and runs the new [job]. */ - private fun startOffsetAnimation(job: () -> Job) { + /** Ends any previous [offsetAnimation] and runs the new [animation]. */ + private fun startOffsetAnimation(animation: () -> OffsetAnimation): OffsetAnimation { cancelOffsetAnimation() - offsetAnimationJob = job() + return animation().also { offsetAnimation = it } } /** Cancel any ongoing offset animation. */ // TODO(b/317063114) This should be a suspended function to avoid multiple jobs running at // the same time. fun cancelOffsetAnimation() { - offsetAnimationJob?.cancel() - finishOffsetAnimation() - } + val animation = offsetAnimation ?: return + offsetAnimation = null - fun finishOffsetAnimation() { - if (isAnimatingOffset) { - isAnimatingOffset = false - dragOffset = offsetAnimatable.value - } + dragOffset = animation.animatable.value + animation.job.cancel() } fun animateOffset( @@ -673,32 +686,74 @@ private class SwipeTransition( coroutineScope: CoroutineScope, initialVelocity: Float, targetOffset: Float, - onAnimationCompleted: () -> Unit, - ) { - startOffsetAnimation { - coroutineScope.launch { - animateOffset(targetOffset, initialVelocity) - onAnimationCompleted() - } + targetScene: SceneKey, + ): OffsetAnimation { + return startOffsetAnimation { + val animatable = Animatable(dragOffset, OffsetVisibilityThreshold) + val job = + coroutineScope + .launch { + animatable.animateTo( + targetValue = targetOffset, + animationSpec = swipeSpec, + initialVelocity = initialVelocity, + ) + } + // Make sure that we settle to target scene at the end of the animation or if + // the animation is cancelled. + .apply { invokeOnCompletion { snapToScene(targetScene) } } + + OffsetAnimation(animatable, job) } } - private suspend fun animateOffset(targetOffset: Float, initialVelocity: Float) { - if (!isAnimatingOffset) { - offsetAnimatable.snapTo(dragOffset) + fun snapToScene(scene: SceneKey) { + if (layoutState.transitionState != this) return + cancelOffsetAnimation() + layoutState.finishTransition(this, idleScene = scene) + } + + override fun finish(): Job { + if (isFinishing) return requireNotNull(offsetAnimation).job + isFinishing = true + + // If we were already animating the offset, simply return the job. + offsetAnimation?.let { + return it.job } - isAnimatingOffset = true - val animationSpec = transformationSpec - offsetAnimatable.animateTo( - targetValue = targetOffset, - animationSpec = swipeSpec, - initialVelocity = initialVelocity, - ) + // Animate to the current scene. + val targetScene = currentScene + val targetOffset = + if (targetScene == fromScene) { + 0f + } else { + val distance = distance() + check(distance != DistanceUnspecified) { + "targetScene != fromScene but distance is unspecified" + } + distance + } - finishOffsetAnimation() + val animation = + animateOffset( + coroutineScope = coroutineScope, + initialVelocity = 0f, + targetOffset = targetOffset, + targetScene = currentScene, + ) + check(offsetAnimation == animation) + return animation.job } + internal class OffsetAnimation( + /** The animatable used to animate the offset. */ + val animatable: Animatable<Float, AnimationVector1D>, + + /** The job in which [animatable] is animated. */ + val job: Job, + ) + companion object { const val DistanceUnspecified = 0f } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt index cdc4778dbf4d..be066fd0018a 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt @@ -133,18 +133,14 @@ private class MovableElementScopeImpl( if (shouldComposeMovableElement) { val movableContent: MovableElementContent = layoutImpl.movableContents[element] - ?: movableContentOf { - contentScope: MovableElementContentScope, - content: @Composable MovableElementContentScope.() -> Unit -> - contentScope.content() - } + ?: movableContentOf { content: @Composable () -> Unit -> content() } .also { layoutImpl.movableContents[element] = it } // Important: Don't introduce any parent Box or other layout here, because contentScope // delegates its BoxScope implementation to the Box where this content() function is // called, so it's important that this movableContent is composed directly under that // Box. - movableContent(contentScope, content) + movableContent { contentScope.content() } } else { // If we are not composed, we still need to lay out an empty space with the same *target // size* as its movable content, i.e. the same *size when idle*. During transitions, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt index 1670e9cee731..25b0895fafb3 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt @@ -38,15 +38,8 @@ import androidx.compose.ui.util.fastForEach import com.android.compose.ui.util.lerp import kotlinx.coroutines.CoroutineScope -/** - * The type for the content of movable elements. - * - * TODO(b/317972419): Revert back to make this movable content have a single @Composable lambda - * parameter. - */ -internal typealias MovableElementContent = - @Composable - (MovableElementContentScope, @Composable MovableElementContentScope.() -> Unit) -> Unit +/** The type for the content of movable elements. */ +internal typealias MovableElementContent = @Composable (@Composable () -> Unit) -> Unit @Stable internal class SceneTransitionLayoutImpl( diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index 0fa19bb33818..86124df295b4 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -31,6 +31,7 @@ import com.android.compose.animation.scene.transition.link.LinkedTransition import com.android.compose.animation.scene.transition.link.StateLink import kotlin.math.absoluteValue import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job import kotlinx.coroutines.channels.Channel /** @@ -188,13 +189,8 @@ sealed interface TransitionState { val fromScene: SceneKey, /** The scene this transition is going to. Can't be the same as fromScene */ - val toScene: SceneKey + val toScene: SceneKey, ) : TransitionState { - - init { - check(fromScene != toScene) - } - /** * The progress of the transition. This is usually in the `[0; 1]` range, but it can also be * less than `0` or greater than `1` when using transitions with a spring AnimationSpec or @@ -208,6 +204,21 @@ sealed interface TransitionState { /** Whether user input is currently driving the transition. */ abstract val isUserInputOngoing: Boolean + init { + check(fromScene != toScene) + } + + /** + * Force this transition to finish and animate to [currentScene], so that this transition + * progress will settle to either 0% (if [currentScene] == [fromScene]) or 100% (if + * [currentScene] == [toScene]) in a finite amount of time. + * + * @return the [Job] that animates the progress to [currentScene]. It can be used to wait + * until the animation is complete or cancel it to snap to [currentScene]. Calling + * [finish] multiple times will return the same [Job]. + */ + abstract fun finish(): Job + /** * Whether we are transitioning. If [from] or [to] is empty, we will also check that they * match the scenes we are animating from and/or to. diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt index 33b57b25fd10..73393a1ab0cf 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt @@ -18,6 +18,7 @@ package com.android.compose.animation.scene.transition.link import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionState +import kotlinx.coroutines.Job /** A linked transition which is driven by a [originalTransition]. */ internal class LinkedTransition( @@ -43,4 +44,6 @@ internal class LinkedTransition( override val progress: Float get() = originalTransition.progress + + override fun finish(): Job = originalTransition.finish() } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt index eb9b4280aacb..1e9a7e2bb667 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt @@ -39,6 +39,7 @@ import com.android.compose.test.runMonotonicClockTest import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.launch import org.junit.Test import org.junit.runner.RunWith @@ -892,6 +893,50 @@ class DraggableHandlerTest { } @Test + fun finish() = runGestureTest { + // Start at scene C. + navigateToSceneC() + + // Swipe up from the middle to transition to scene B. + val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) + onDragStarted(startedPosition = middle, overSlop = up(0.1f)) + assertTransition(fromScene = SceneC, toScene = SceneB, isUserInputOngoing = true) + + // The current transition can be intercepted. + assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isTrue() + + // Finish the transition. + val transition = transitionState as Transition + val job = transition.finish() + assertTransition(isUserInputOngoing = false) + + // The current transition can not be intercepted anymore. + assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isFalse() + + // Calling finish() multiple times returns the same Job. + assertThat(transition.finish()).isSameInstanceAs(job) + assertThat(transition.finish()).isSameInstanceAs(job) + assertThat(transition.finish()).isSameInstanceAs(job) + + // We can join the job to wait for the animation to end. + assertTransition() + job.join() + assertIdle(SceneC) + } + + @Test + fun finish_cancelled() = runGestureTest { + // Swipe up from the middle to transition to scene B. + val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) + onDragStarted(startedPosition = middle, overSlop = up(0.1f)) + assertTransition(fromScene = SceneA, toScene = SceneB) + + // Finish the transition and cancel the returned job. + (transitionState as Transition).finish().cancelAndJoin() + assertIdle(SceneA) + } + + @Test fun blockTransition() = runGestureTest { assertIdle(SceneA) @@ -951,4 +996,15 @@ class DraggableHandlerTest { assertThat(transition).isNotNull() assertThat(transition!!.progress).isEqualTo(-0.1f) } + + @Test + fun transitionIsImmediatelyUpdatedWhenReleasingFinger() = runGestureTest { + // Swipe up from the middle to transition to scene B. + val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) + val dragController = onDragStarted(startedPosition = middle, overSlop = up(0.1f)) + assertTransition(fromScene = SceneA, toScene = SceneB, isUserInputOngoing = true) + + dragController.onDragStopped(velocity = 0f) + assertTransition(isUserInputOngoing = false) + } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt index 3cbcd73a0adb..9baabc3cfb57 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt @@ -28,6 +28,8 @@ import com.android.compose.animation.scene.transition.link.StateLink import com.android.compose.test.runMonotonicClockTest import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.job import kotlinx.coroutines.launch import org.junit.Rule import org.junit.Test @@ -37,16 +39,6 @@ import org.junit.runner.RunWith class SceneTransitionLayoutStateTest { @get:Rule val rule = createComposeRule() - class TestableTransition( - fromScene: SceneKey, - toScene: SceneKey, - ) : TransitionState.Transition(fromScene, toScene) { - override var currentScene: SceneKey = fromScene - override var progress: Float = 0.0f - override var isInitiatedByUserInput: Boolean = false - override var isUserInputOngoing: Boolean = false - } - @Test fun isTransitioningTo_idle() { val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty) @@ -83,25 +75,31 @@ class SceneTransitionLayoutStateTest { assertThat(transition).isNotNull() assertThat(state.transitionState).isEqualTo(transition) - testScheduler.advanceUntilIdle() + transition!!.finish().join() assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB)) } @Test fun setTargetScene_transitionToSameScene() = runMonotonicClockTest { val state = MutableSceneTransitionLayoutState(SceneA) - assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull() + + val transition = state.setTargetScene(SceneB, coroutineScope = this) + assertThat(transition).isNotNull() assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNull() - testScheduler.advanceUntilIdle() + + transition!!.finish().join() assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB)) } @Test fun setTargetScene_transitionToDifferentScene() = runMonotonicClockTest { val state = MutableSceneTransitionLayoutState(SceneA) + assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull() - assertThat(state.setTargetScene(SceneC, coroutineScope = this)).isNotNull() - testScheduler.advanceUntilIdle() + val transition = state.setTargetScene(SceneC, coroutineScope = this) + assertThat(transition).isNotNull() + + transition!!.finish().join() assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneC)) } @@ -127,11 +125,22 @@ class SceneTransitionLayoutStateTest { assertThat(state.transitionState).isEqualTo(transition) // Cancelling the scope/job still sets the state to Idle(targetScene). - job.cancel() - testScheduler.advanceUntilIdle() + job.cancelAndJoin() assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB)) } + @Test + fun transition_finishReturnsTheSameJobWhenCalledMultipleTimes() = runMonotonicClockTest { + val state = MutableSceneTransitionLayoutState(SceneA) + val transition = state.setTargetScene(SceneB, coroutineScope = this) + assertThat(transition).isNotNull() + + val job = transition!!.finish() + assertThat(transition.finish()).isSameInstanceAs(job) + assertThat(transition.finish()).isSameInstanceAs(job) + assertThat(transition.finish()).isSameInstanceAs(job) + } + private fun setupLinkedStates( parentInitialScene: SceneKey = SceneC, childInitialScene: SceneKey = SceneA, @@ -159,7 +168,7 @@ class SceneTransitionLayoutStateTest { fun linkedTransition_startsLinkAndFinishesLinkInToState() { val (parentState, childState) = setupLinkedStates() - val childTransition = TestableTransition(SceneA, SceneB) + val childTransition = transition(SceneA, SceneB) childState.startTransition(childTransition, null) assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue() @@ -195,7 +204,7 @@ class SceneTransitionLayoutStateTest { MutableSceneTransitionLayoutState(SceneA, stateLinks = link) as BaseSceneTransitionLayoutState - val childTransition = TestableTransition(SceneA, SceneB) + val childTransition = transition(SceneA, SceneB) childState.startTransition(childTransition, null) assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue() @@ -212,12 +221,13 @@ class SceneTransitionLayoutStateTest { fun linkedTransition_linkProgressIsEqual() { val (parentState, childState) = setupLinkedStates() - val childTransition = TestableTransition(SceneA, SceneB) + var progress = 0f + val childTransition = transition(SceneA, SceneB, progress = { progress }) childState.startTransition(childTransition, null) assertThat(parentState.currentTransition?.progress).isEqualTo(0f) - childTransition.progress = .5f + progress = .5f assertThat(parentState.currentTransition?.progress).isEqualTo(.5f) } @@ -225,7 +235,7 @@ class SceneTransitionLayoutStateTest { fun linkedTransition_reverseTransitionIsNotLinked() { val (parentState, childState) = setupLinkedStates() - val childTransition = TestableTransition(SceneB, SceneA) + val childTransition = transition(SceneB, SceneA) childState.startTransition(childTransition, null) assertThat(childState.isTransitioning(SceneB, SceneA)).isTrue() @@ -240,7 +250,7 @@ class SceneTransitionLayoutStateTest { fun linkedTransition_startsLinkAndFinishesLinkInFromState() { val (parentState, childState) = setupLinkedStates() - val childTransition = TestableTransition(SceneA, SceneB) + val childTransition = transition(SceneA, SceneB) childState.startTransition(childTransition, null) childState.finishTransition(childTransition, SceneA) @@ -252,7 +262,7 @@ class SceneTransitionLayoutStateTest { fun linkedTransition_startsLinkAndFinishesLinkInUnknownState() { val (parentState, childState) = setupLinkedStates() - val childTransition = TestableTransition(SceneA, SceneB) + val childTransition = transition(SceneA, SceneB) childState.startTransition(childTransition, null) childState.finishTransition(childTransition, SceneD) @@ -264,8 +274,8 @@ class SceneTransitionLayoutStateTest { fun linkedTransition_startsLinkButLinkedStateIsTakenOver() { val (parentState, childState) = setupLinkedStates() - val childTransition = TestableTransition(SceneA, SceneB) - val parentTransition = TestableTransition(SceneC, SceneA) + val childTransition = transition(SceneA, SceneB) + val parentTransition = transition(SceneC, SceneA) childState.startTransition(childTransition, null) parentState.startTransition(parentTransition, null) @@ -315,9 +325,9 @@ class SceneTransitionLayoutStateTest { @Test fun snapToIdleIfClose_snapToStart() = runMonotonicClockTest { - val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty) + val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty) state.startTransition( - transition(from = TestScenes.SceneA, to = TestScenes.SceneB, progress = { 0.2f }), + transition(from = SceneA, to = TestScenes.SceneB, progress = { 0.2f }), transitionKey = null ) assertThat(state.isTransitioning()).isTrue() @@ -329,14 +339,14 @@ class SceneTransitionLayoutStateTest { // Go to the initial scene if it is close to 0. assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue() assertThat(state.isTransitioning()).isFalse() - assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneA)) + assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA)) } @Test fun snapToIdleIfClose_snapToEnd() = runMonotonicClockTest { - val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty) + val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty) state.startTransition( - transition(from = TestScenes.SceneA, to = TestScenes.SceneB, progress = { 0.8f }), + transition(from = SceneA, to = TestScenes.SceneB, progress = { 0.8f }), transitionKey = null ) assertThat(state.isTransitioning()).isTrue() @@ -354,7 +364,7 @@ class SceneTransitionLayoutStateTest { @Test fun linkedTransition_fuzzyLinksAreMatchedAndStarted() { val (parentState, childState) = setupLinkedStates(SceneC, SceneA, null, null, null, SceneD) - val childTransition = TestableTransition(SceneA, SceneB) + val childTransition = transition(SceneA, SceneB) childState.startTransition(childTransition, null) assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue() @@ -370,7 +380,7 @@ class SceneTransitionLayoutStateTest { val (parentState, childState) = setupLinkedStates(SceneC, SceneA, SceneA, null, null, SceneD) - val childTransition = TestableTransition(SceneA, SceneB) + val childTransition = transition(SceneA, SceneB) childState.startTransition(childTransition, null) assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue() @@ -385,7 +395,7 @@ class SceneTransitionLayoutStateTest { fun linkedTransition_fuzzyLinksAreNotMatched() { val (parentState, childState) = setupLinkedStates(SceneC, SceneA, SceneB, null, SceneC, SceneD) - val childTransition = TestableTransition(SceneA, SceneB) + val childTransition = transition(SceneA, SceneB) childState.startTransition(childTransition, null) assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue() @@ -402,18 +412,12 @@ class SceneTransitionLayoutStateTest { sceneTransitions, ) state.startTransition( - object : - TransitionState.Transition(SceneA, SceneB), - TransitionState.HasOverscrollProperties { - override val currentScene: SceneKey = SceneA - override val progress: Float - get() = progress() - - override val isInitiatedByUserInput: Boolean = false - override val isUserInputOngoing: Boolean = false - override val isUpOrLeft: Boolean = false - override val orientation: Orientation = Orientation.Vertical - }, + transition( + from = SceneA, + to = SceneB, + progress = progress, + orientation = Orientation.Vertical, + ), transitionKey = null ) assertThat(state.isTransitioning()).isTrue() diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt index 238b21e1ea37..153d2b8769b3 100644 --- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt +++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt @@ -16,6 +16,9 @@ package com.android.compose.animation.scene +import androidx.compose.foundation.gestures.Orientation +import kotlinx.coroutines.Job + /** A utility to easily create a [TransitionState.Transition] in tests. */ fun transition( from: SceneKey, @@ -23,11 +26,21 @@ fun transition( progress: () -> Float = { 0f }, isInitiatedByUserInput: Boolean = false, isUserInputOngoing: Boolean = false, + isUpOrLeft: Boolean = false, + orientation: Orientation = Orientation.Horizontal, ): TransitionState.Transition { - return object : TransitionState.Transition(from, to) { + return object : TransitionState.Transition(from, to), TransitionState.HasOverscrollProperties { override val currentScene: SceneKey = from - override val progress: Float = progress() + override val progress: Float + get() = progress() + override val isInitiatedByUserInput: Boolean = isInitiatedByUserInput override val isUserInputOngoing: Boolean = isUserInputOngoing + override val isUpOrLeft: Boolean = isUpOrLeft + override val orientation: Orientation = orientation + + override fun finish(): Job { + error("finish() is not supported in test transitions") + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt new file mode 100644 index 000000000000..def63ec5b171 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal + +import android.service.dream.dreamManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testScope +import com.android.systemui.power.data.repository.fakePowerRepository +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.power.shared.model.ScreenPowerState +import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.whenever +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class CommunalDreamStartableTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private lateinit var underTest: CommunalDreamStartable + + private val dreamManager by lazy { kosmos.dreamManager } + private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository } + private val powerRepository by lazy { kosmos.fakePowerRepository } + + @Before + fun setUp() { + underTest = + CommunalDreamStartable( + powerInteractor = kosmos.powerInteractor, + keyguardInteractor = kosmos.keyguardInteractor, + keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor, + dreamManager = dreamManager, + bgScope = kosmos.applicationCoroutineScope, + ) + .apply { start() } + } + + @Test + fun startDreamWhenTransitioningToHub() = + testScope.runTest { + keyguardRepository.setDreaming(false) + powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON) + whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true) + runCurrent() + + verify(dreamManager, never()).startDream() + + transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB) + + verify(dreamManager).startDream() + } + + @Test + fun shouldNotStartDreamWhenIneligibleToDream() = + testScope.runTest { + keyguardRepository.setDreaming(false) + powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON) + // Not eligible to dream + whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(false) + transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB) + + verify(dreamManager, never()).startDream() + } + + @Test + fun shouldNotStartDreamIfAlreadyDreaming() = + testScope.runTest { + keyguardRepository.setDreaming(true) + powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON) + whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true) + transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB) + + verify(dreamManager, never()).startDream() + } + + @Test + fun shouldNotStartDreamForInvalidTransition() = + testScope.runTest { + keyguardRepository.setDreaming(true) + powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON) + whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true) + + // Verify we do not trigger dreaming for any other state besides glanceable hub + for (state in KeyguardState.entries) { + if (state == KeyguardState.GLANCEABLE_HUB) continue + transition(from = KeyguardState.GLANCEABLE_HUB, to = state) + verify(dreamManager, never()).startDream() + } + } + + private suspend fun TestScope.transition(from: KeyguardState, to: KeyguardState) { + kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( + from = from, + to = to, + testScope = this + ) + runCurrent() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index 1a4f61e36b36..a5707e1f6fe4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -22,6 +22,8 @@ import android.appwidget.AppWidgetProviderInfo import android.content.Intent import android.content.pm.UserInfo import android.os.UserHandle +import android.os.UserManager +import android.os.userManager import android.provider.Settings import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED import android.widget.RemoteViews @@ -65,6 +67,7 @@ import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.data.repository.fakeUserRepository +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.mock @@ -79,6 +82,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito.mock @@ -111,6 +115,7 @@ class CommunalInteractorTest : SysuiTestCase() { private lateinit var sceneInteractor: SceneInteractor private lateinit var userTracker: FakeUserTracker private lateinit var activityStarter: ActivityStarter + private lateinit var userManager: UserManager private lateinit var underTest: CommunalInteractor @@ -130,9 +135,12 @@ class CommunalInteractorTest : SysuiTestCase() { sceneInteractor = kosmos.sceneInteractor userTracker = kosmos.fakeUserTracker activityStarter = kosmos.activityStarter + userManager = kosmos.userManager whenever(mainUser.isMain).thenReturn(true) whenever(secondaryUser.isMain).thenReturn(false) + whenever(userManager.isQuietModeEnabled(any<UserHandle>())).thenReturn(false) + whenever(userManager.isManagedProfile(anyInt())).thenReturn(false) userRepository.setUserInfos(listOf(mainUser, secondaryUser)) kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true) @@ -851,6 +859,63 @@ class CommunalInteractorTest : SysuiTestCase() { } @Test + fun widgetContent_inQuietMode() = + testScope.runTest { + // Keyguard showing, and tutorial completed. + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setKeyguardOccluded(false) + tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + + // Work profile is set up. + val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK) + userRepository.setUserInfos(userInfos) + userTracker.set( + userInfos = userInfos, + selectedUserIndex = 0, + ) + runCurrent() + + // Keyguard widgets are allowed. + kosmos.fakeSettings.putIntForUser( + CommunalSettingsRepositoryImpl.GLANCEABLE_HUB_CONTENT_SETTING, + AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD, + mainUser.id + ) + runCurrent() + + // When work profile is paused. + whenever(userManager.isQuietModeEnabled(eq(UserHandle.of(USER_INFO_WORK.id)))) + .thenReturn(true) + whenever(userManager.isManagedProfile(eq(USER_INFO_WORK.id))).thenReturn(true) + + val widgetContent by collectLastValue(underTest.widgetContent) + val widget1 = createWidgetForUser(1, USER_INFO_WORK.id) + val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id) + val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id) + val widgets = listOf(widget1, widget2, widget3) + widgetRepository.setCommunalWidgets(widgets) + + // The work profile widget is in quiet mode, while other widgets are not. + assertThat(widgetContent).hasSize(3) + widgetContent!!.forEach { model -> + assertThat(model) + .isInstanceOf(CommunalContentModel.WidgetContent.Widget::class.java) + } + assertThat( + (widgetContent!![0] as CommunalContentModel.WidgetContent.Widget).inQuietMode + ) + .isTrue() + assertThat( + (widgetContent!![1] as CommunalContentModel.WidgetContent.Widget).inQuietMode + ) + .isFalse() + assertThat( + (widgetContent!![2] as CommunalContentModel.WidgetContent.Widget).inQuietMode + ) + .isFalse() + } + + @Test fun widgetContent_containsDisabledWidgets_whenCategoryNotAllowed() = testScope.runTest { // Communal available, and tutorial completed. @@ -951,7 +1016,10 @@ class CommunalInteractorTest : SysuiTestCase() { private fun createWidgetForUser(appWidgetId: Int, userId: Int): CommunalWidgetContentModel = mock<CommunalWidgetContentModel> { whenever(this.appWidgetId).thenReturn(appWidgetId) - val providerInfo = mock<AppWidgetProviderInfo>() + val providerInfo = + mock<AppWidgetProviderInfo>().apply { + widgetCategory = AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD + } whenever(providerInfo.profile).thenReturn(UserHandle(userId)) whenever(this.providerInfo).thenReturn(providerInfo) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt index c670506d9f04..86fdaa5872e8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt @@ -8,7 +8,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.complication.ComplicationHostViewController -import com.android.systemui.dreams.ui.viewmodel.DreamOverlayViewModel +import com.android.systemui.dreams.ui.viewmodel.DreamViewModel import com.android.systemui.log.core.FakeLogBuffer import com.android.systemui.statusbar.BlurUtils import com.android.systemui.util.mockito.argumentCaptor @@ -45,7 +45,7 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() { @Mock private lateinit var hostViewController: ComplicationHostViewController @Mock private lateinit var statusBarViewController: DreamOverlayStatusBarViewController @Mock private lateinit var stateController: DreamOverlayStateController - @Mock private lateinit var transitionViewModel: DreamOverlayViewModel + @Mock private lateinit var transitionViewModel: DreamViewModel private val logBuffer = FakeLogBuffer.Factory.create() private lateinit var controller: DreamOverlayAnimationsController diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt index 9da34da451c4..e34edb4c442e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt @@ -26,14 +26,9 @@ import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository -import com.android.systemui.keyguard.shared.model.KeyguardState.AOD -import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING -import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.TransitionState -import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED -import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING import com.android.systemui.keyguard.shared.model.TransitionState.STARTED import com.android.systemui.keyguard.shared.model.TransitionStep @@ -219,37 +214,6 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) } } - @Test - fun transitionEnded() = - testScope.runTest { - val values by collectValues(underTest.transitionEnded) - - keyguardTransitionRepository.sendTransitionSteps( - listOf( - TransitionStep(DOZING, DREAMING, 0.0f, STARTED), - TransitionStep(DOZING, DREAMING, 1.0f, FINISHED), - TransitionStep(DREAMING, LOCKSCREEN, 0.0f, STARTED), - TransitionStep(DREAMING, LOCKSCREEN, 0.1f, RUNNING), - TransitionStep(DREAMING, LOCKSCREEN, 1.0f, FINISHED), - TransitionStep(LOCKSCREEN, DREAMING, 0.0f, STARTED), - TransitionStep(LOCKSCREEN, DREAMING, 0.5f, RUNNING), - TransitionStep(LOCKSCREEN, DREAMING, 1.0f, FINISHED), - TransitionStep(DREAMING, GONE, 0.0f, STARTED), - TransitionStep(DREAMING, GONE, 0.5f, RUNNING), - TransitionStep(DREAMING, GONE, 1.0f, CANCELED), - TransitionStep(DREAMING, AOD, 0.0f, STARTED), - TransitionStep(DREAMING, AOD, 1.0f, FINISHED), - ), - testScope, - ) - - assertThat(values.size).isEqualTo(3) - values.forEach { - assertThat(it.transitionState == FINISHED || it.transitionState == CANCELED) - .isTrue() - } - } - private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep { return TransitionStep( from = DREAMING, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt index 4fe45f614fdf..8c9036a4c68e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.userRepository import com.google.common.truth.Truth @@ -562,17 +563,17 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { } @Test - fun isSplitShade() = + fun shadeMode() = testScope.runTest { - val isSplitShade by collectLastValue(underTest.isSplitShade) + val shadeMode by collectLastValue(underTest.shadeMode) - shadeRepository.setSplitShade(true) - assertThat(isSplitShade).isTrue() + shadeRepository.setShadeMode(ShadeMode.Split) + assertThat(shadeMode).isEqualTo(ShadeMode.Split) - shadeRepository.setSplitShade(false) - assertThat(isSplitShade).isFalse() + shadeRepository.setShadeMode(ShadeMode.Single) + assertThat(shadeMode).isEqualTo(ShadeMode.Single) - shadeRepository.setSplitShade(true) - assertThat(isSplitShade).isTrue() + shadeRepository.setShadeMode(ShadeMode.Split) + assertThat(shadeMode).isEqualTo(ShadeMode.Split) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt index cbb84dad5e42..31dacdd61151 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt @@ -24,6 +24,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest @@ -42,20 +43,20 @@ class ShadeStartableTest : SysuiTestCase() { private val underTest = kosmos.shadeStartable @Test - fun hydrateSplitShade() = + fun hydrateShadeMode() = testScope.runTest { overrideResource(R.bool.config_use_split_notification_shade, false) - val isSplitShade by collectLastValue(shadeInteractor.isSplitShade) + val shadeMode by collectLastValue(shadeInteractor.shadeMode) underTest.start() - assertThat(isSplitShade).isFalse() + assertThat(shadeMode).isEqualTo(ShadeMode.Single) overrideResource(R.bool.config_use_split_notification_shade, true) fakeConfigurationRepository.onAnyConfigurationChange() - assertThat(isSplitShade).isTrue() + assertThat(shadeMode).isEqualTo(ShadeMode.Split) overrideResource(R.bool.config_use_split_notification_shade, false) fakeConfigurationRepository.onAnyConfigurationChange() - assertThat(isSplitShade).isFalse() + assertThat(shadeMode).isEqualTo(ShadeMode.Single) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index 2e68d12cebee..1c5496142fec 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -37,10 +37,12 @@ import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.domain.interactor.privacyChipInteractor import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shade.domain.startable.shadeStartable +import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor @@ -72,6 +74,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { private val testScope = kosmos.testScope private val sceneInteractor by lazy { kosmos.sceneInteractor } private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor } + private val shadeRepository by lazy { kosmos.shadeRepository } private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) } @@ -274,4 +277,19 @@ class ShadeSceneViewModelTest : SysuiTestCase() { assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.toScene) .isEqualTo(Scenes.QuickSettings) } + + @Test + fun shadeMode() = + testScope.runTest { + val shadeMode by collectLastValue(underTest.shadeMode) + + shadeRepository.setShadeMode(ShadeMode.Split) + assertThat(shadeMode).isEqualTo(ShadeMode.Split) + + shadeRepository.setShadeMode(ShadeMode.Single) + assertThat(shadeMode).isEqualTo(ShadeMode.Single) + + shadeRepository.setShadeMode(ShadeMode.Split) + assertThat(shadeMode).isEqualTo(ShadeMode.Split) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index deb19769372c..e683f34c4ea1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -21,13 +21,10 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository -import com.android.systemui.communal.domain.interactor.communalInteractor -import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic @@ -80,13 +77,13 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { init { kosmos.aodBurnInViewModel = aodBurnInViewModel } + val testScope = kosmos.testScope val configurationRepository = kosmos.fakeConfigurationRepository val keyguardRepository = kosmos.fakeKeyguardRepository val keyguardInteractor = kosmos.keyguardInteractor val keyguardRootViewModel = kosmos.keyguardRootViewModel val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository - val communalInteractor = kosmos.communalInteractor val shadeRepository = kosmos.shadeRepository val sharedNotificationContainerInteractor = kosmos.sharedNotificationContainerInteractor val largeScreenHeaderHelper = kosmos.mockLargeScreenHeaderHelper @@ -239,7 +236,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } @Test - fun glanceableHubAlpha() = + fun glanceableHubAlpha_lockscreenToHub() = testScope.runTest { val alpha by collectLastValue(underTest.glanceableHubAlpha) @@ -278,12 +275,6 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { value = 1f, ) ) - val idleTransitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(CommunalScenes.Communal) - ) - communalInteractor.setTransitionState(idleTransitionState) - runCurrent() assertThat(alpha).isEqualTo(0f) // While state is GLANCEABLE_HUB, verify alpha is restored to full if glanceable hub is @@ -293,6 +284,50 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } @Test + fun glanceableHubAlpha_dreamToHub() = + testScope.runTest { + val alpha by collectLastValue(underTest.glanceableHubAlpha) + + // Start on dream + showDream() + assertThat(alpha).isEqualTo(1f) + + // Start transitioning to glanceable hub + val progress = 0.6f + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.STARTED, + from = KeyguardState.DREAMING, + to = KeyguardState.GLANCEABLE_HUB, + value = 0f, + ) + ) + runCurrent() + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.RUNNING, + from = KeyguardState.DREAMING, + to = KeyguardState.GLANCEABLE_HUB, + value = progress, + ) + ) + runCurrent() + // Keep notifications hidden during the transition from dream to hub + assertThat(alpha).isEqualTo(0) + + // Finish transition to glanceable hub + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.FINISHED, + from = KeyguardState.DREAMING, + to = KeyguardState.GLANCEABLE_HUB, + value = 1f, + ) + ) + assertThat(alpha).isEqualTo(0f) + } + + @Test fun validateMarginTop() = testScope.runTest { overrideResource(R.bool.config_use_large_screen_shade_header, false) @@ -391,12 +426,11 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { assertThat(isOnGlanceableHubWithoutShade).isFalse() // Move to glanceable hub - val idleTransitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(CommunalScenes.Communal) - ) - communalInteractor.setTransitionState(idleTransitionState) - runCurrent() + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GLANCEABLE_HUB, + testScope = this + ) assertThat(isOnGlanceableHubWithoutShade).isTrue() // While state is GLANCEABLE_HUB, validate variations of both shade and qs expansion @@ -726,6 +760,19 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { ) } + private suspend fun TestScope.showDream() { + shadeRepository.setLockscreenShadeExpansion(0f) + shadeRepository.setQsExpansion(0f) + runCurrent() + keyguardRepository.setDreaming(true) + runCurrent() + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DREAMING, + testScope, + ) + } + private suspend fun TestScope.showLockscreenWithShadeExpanded() { shadeRepository.setLockscreenShadeExpansion(1f) shadeRepository.setQsExpansion(0f) diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml index f51e1098f333..7341015e8690 100644 --- a/packages/SystemUI/res-keyguard/values/strings.xml +++ b/packages/SystemUI/res-keyguard/values/strings.xml @@ -304,6 +304,15 @@ <!-- An explanation text that the password needs to be entered since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] --> <string name="kg_prompt_reason_timeout_password">For additional security, use password instead</string> + <!-- An explanation text that the pin needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] --> + <string name="kg_prompt_added_security_pin">PIN required for additional security</string> + + <!-- An explanation text that the pattern needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] --> + <string name="kg_prompt_added_security_pattern">Pattern required for additional security</string> + + <!-- An explanation text that the password needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] --> + <string name="kg_prompt_added_security_password">Password required for additional security</string> + <!-- An explanation text that the credential needs to be entered because a device admin has locked the device. [CHAR LIMIT=80] --> <string name="kg_prompt_reason_device_admin">Device locked by admin</string> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index d8e68f2d5fe7..25596cce3b97 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1139,6 +1139,10 @@ <string name="dialog_title_to_allow_any_widget">Allow any widget on lock screen?</string> <!-- Text for the button in the dialog that opens when tapping on disabled widgets. [CHAR LIMIT=NONE] --> <string name="button_text_to_open_settings">Open settings</string> + <!-- Title of a dialog. This text is confirming that the user wants to turn on access to their work apps, which the user had previously paused. "Work" is an adjective. [CHAR LIMIT=30] --> + <string name="work_mode_off_title">Unpause work apps?</string> + <!-- Title for button to unpause on work profile. [CHAR LIMIT=NONE] --> + <string name="work_mode_turn_on">Unpause</string> <!-- Related to user switcher --><skip/> diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp index 6e611fe51e8d..42ba05cff906 100644 --- a/packages/SystemUI/shared/Android.bp +++ b/packages/SystemUI/shared/Android.bp @@ -35,6 +35,9 @@ java_library { srcs: [ ":statslog-SystemUI-java-gen", ], + libs: [ + "androidx.annotation_annotation", + ], } android_library { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java index 84c8ea708031..26e91b62d19a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -122,7 +122,7 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { case PROMPT_REASON_USER_REQUEST: return R.string.kg_prompt_after_user_lockdown_password; case PROMPT_REASON_PREPARE_FOR_UPDATE: - return R.string.kg_prompt_reason_timeout_password; + return R.string.kg_prompt_added_security_password; case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT: return R.string.kg_prompt_reason_timeout_password; case PROMPT_REASON_TRUSTAGENT_EXPIRED: diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java index bf8900da887a..caa74780538e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java @@ -323,7 +323,7 @@ public class KeyguardPatternViewController resId = R.string.kg_prompt_after_user_lockdown_pattern; break; case PROMPT_REASON_PREPARE_FOR_UPDATE: - resId = R.string.kg_prompt_reason_timeout_pattern; + resId = R.string.kg_prompt_added_security_pattern; break; case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT: resId = R.string.kg_prompt_reason_timeout_pattern; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java index bcab6f054dd6..fbe9edfd6680 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -134,7 +134,7 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView case PROMPT_REASON_USER_REQUEST: return R.string.kg_prompt_after_user_lockdown_pin; case PROMPT_REASON_PREPARE_FOR_UPDATE: - return R.string.kg_prompt_reason_timeout_pin; + return R.string.kg_prompt_added_security_pin; case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT: return R.string.kg_prompt_reason_timeout_pin; case PROMPT_REASON_TRUSTAGENT_EXPIRED: diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt index aab0b1e99e09..e88aaf015f87 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt +++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt @@ -22,6 +22,7 @@ import android.content.BroadcastReceiver import android.content.ContentProvider import android.content.Context import android.content.Intent +import android.util.Log import androidx.core.app.AppComponentFactory import com.android.systemui.dagger.ContextComponentHelper import com.android.systemui.dagger.SysUIComponent @@ -90,8 +91,7 @@ abstract class SystemUIAppComponentFactoryBase : AppComponentFactory() { return app } - @UsesReflection( - KeepTarget(instanceOfClassConstant = SysUIComponent::class, methodName = "inject")) + @UsesReflection(KeepTarget(instanceOfClassConstant = SysUIComponent::class, methodName = "inject")) override fun instantiateProviderCompat(cl: ClassLoader, className: String): ContentProvider { val contentProvider = super.instantiateProviderCompat(cl, className) if (contentProvider is ContextInitializer) { @@ -103,12 +103,11 @@ abstract class SystemUIAppComponentFactoryBase : AppComponentFactory() { .getMethod("inject", contentProvider.javaClass) injectMethod.invoke(rootComponent, contentProvider) } catch (e: NoSuchMethodException) { - throw RuntimeException("No injector for class: " + contentProvider.javaClass, e) + Log.w(TAG, "No injector for class: " + contentProvider.javaClass, e) } catch (e: IllegalAccessException) { - throw RuntimeException("Injector inaccessible for class: " + - contentProvider.javaClass, e) + Log.w(TAG, "No injector for class: " + contentProvider.javaClass, e) } catch (e: InvocationTargetException) { - throw RuntimeException("Error while injecting: " + contentProvider.javaClass, e) + Log.w(TAG, "No injector for class: " + contentProvider.javaClass, e) } initializer } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt index c25e748f8668..3092defc8b0e 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt @@ -23,10 +23,12 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.Flags +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.biometrics.data.repository.FacePropertyRepository import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.bouncer.data.repository.BouncerMessageRepository import com.android.systemui.bouncer.shared.model.BouncerMessageModel +import com.android.systemui.bouncer.shared.model.BouncerMessageStrings import com.android.systemui.bouncer.shared.model.Message import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -35,46 +37,6 @@ import com.android.systemui.flags.SystemPropertiesHelper import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.TrustRepository -import com.android.systemui.res.R.string.bouncer_face_not_recognized -import com.android.systemui.res.R.string.keyguard_enter_password -import com.android.systemui.res.R.string.keyguard_enter_pattern -import com.android.systemui.res.R.string.keyguard_enter_pin -import com.android.systemui.res.R.string.kg_bio_too_many_attempts_password -import com.android.systemui.res.R.string.kg_bio_too_many_attempts_pattern -import com.android.systemui.res.R.string.kg_bio_too_many_attempts_pin -import com.android.systemui.res.R.string.kg_bio_try_again_or_password -import com.android.systemui.res.R.string.kg_bio_try_again_or_pattern -import com.android.systemui.res.R.string.kg_bio_try_again_or_pin -import com.android.systemui.res.R.string.kg_face_locked_out -import com.android.systemui.res.R.string.kg_fp_not_recognized -import com.android.systemui.res.R.string.kg_primary_auth_locked_out_password -import com.android.systemui.res.R.string.kg_primary_auth_locked_out_pattern -import com.android.systemui.res.R.string.kg_primary_auth_locked_out_pin -import com.android.systemui.res.R.string.kg_prompt_after_adaptive_auth_lock -import com.android.systemui.res.R.string.kg_prompt_after_dpm_lock -import com.android.systemui.res.R.string.kg_prompt_after_update_password -import com.android.systemui.res.R.string.kg_prompt_after_update_pattern -import com.android.systemui.res.R.string.kg_prompt_after_update_pin -import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_password -import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_pattern -import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_pin -import com.android.systemui.res.R.string.kg_prompt_auth_timeout -import com.android.systemui.res.R.string.kg_prompt_password_auth_timeout -import com.android.systemui.res.R.string.kg_prompt_pattern_auth_timeout -import com.android.systemui.res.R.string.kg_prompt_pin_auth_timeout -import com.android.systemui.res.R.string.kg_prompt_reason_restart_password -import com.android.systemui.res.R.string.kg_prompt_reason_restart_pattern -import com.android.systemui.res.R.string.kg_prompt_reason_restart_pin -import com.android.systemui.res.R.string.kg_prompt_unattended_update -import com.android.systemui.res.R.string.kg_too_many_failed_attempts_countdown -import com.android.systemui.res.R.string.kg_trust_agent_disabled -import com.android.systemui.res.R.string.kg_unlock_with_password_or_fp -import com.android.systemui.res.R.string.kg_unlock_with_pattern_or_fp -import com.android.systemui.res.R.string.kg_unlock_with_pin_or_fp -import com.android.systemui.res.R.string.kg_wrong_input_try_fp_suggestion -import com.android.systemui.res.R.string.kg_wrong_password_try_again -import com.android.systemui.res.R.string.kg_wrong_pattern_try_again -import com.android.systemui.res.R.string.kg_wrong_pin_try_again import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.Quint import javax.inject.Inject @@ -130,17 +92,22 @@ constructor( repository.setMessage( when (biometricSourceType) { BiometricSourceType.FINGERPRINT -> - incorrectFingerprintInput(currentSecurityMode) + BouncerMessageStrings.incorrectFingerprintInput( + currentSecurityMode.toAuthModel() + ) + .toMessage() BiometricSourceType.FACE -> - incorrectFaceInput( - currentSecurityMode, - isFingerprintAuthCurrentlyAllowed.value - ) + BouncerMessageStrings.incorrectFaceInput( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() else -> - defaultMessage( - currentSecurityMode, - isFingerprintAuthCurrentlyAllowed.value - ) + BouncerMessageStrings.defaultMessage( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() } ) } @@ -189,45 +156,79 @@ constructor( trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterReboot ) { if (wasRebootedForMainlineUpdate) { - authRequiredForMainlineUpdate(currentSecurityMode) + BouncerMessageStrings.authRequiredForMainlineUpdate( + currentSecurityMode.toAuthModel() + ) + .toMessage() } else { - authRequiredAfterReboot(currentSecurityMode) + BouncerMessageStrings.authRequiredAfterReboot( + currentSecurityMode.toAuthModel() + ) + .toMessage() } } else if (trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterTimeout) { - authRequiredAfterPrimaryAuthTimeout(currentSecurityMode) + BouncerMessageStrings.authRequiredAfterPrimaryAuthTimeout( + currentSecurityMode.toAuthModel() + ) + .toMessage() } else if (flags.isPrimaryAuthRequiredAfterDpmLockdown) { - authRequiredAfterAdminLockdown(currentSecurityMode) + BouncerMessageStrings.authRequiredAfterAdminLockdown( + currentSecurityMode.toAuthModel() + ) + .toMessage() } else if ( trustOrBiometricsAvailable && flags.primaryAuthRequiredForUnattendedUpdate ) { - authRequiredForUnattendedUpdate(currentSecurityMode) + BouncerMessageStrings.authRequiredForUnattendedUpdate( + currentSecurityMode.toAuthModel() + ) + .toMessage() } else if (fpLockedOut) { - class3AuthLockedOut(currentSecurityMode) + BouncerMessageStrings.class3AuthLockedOut(currentSecurityMode.toAuthModel()) + .toMessage() } else if (faceLockedOut) { if (isFaceAuthClass3) { - class3AuthLockedOut(currentSecurityMode) + BouncerMessageStrings.class3AuthLockedOut(currentSecurityMode.toAuthModel()) + .toMessage() } else { - faceLockedOut(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value) + BouncerMessageStrings.faceLockedOut( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() } } else if (flags.isSomeAuthRequiredAfterAdaptiveAuthRequest) { - authRequiredAfterAdaptiveAuthRequest( - currentSecurityMode, - isFingerprintAuthCurrentlyAllowed.value - ) + BouncerMessageStrings.authRequiredAfterAdaptiveAuthRequest( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() } else if ( trustOrBiometricsAvailable && flags.strongerAuthRequiredAfterNonStrongBiometricsTimeout ) { - nonStrongAuthTimeout( - currentSecurityMode, - isFingerprintAuthCurrentlyAllowed.value - ) + BouncerMessageStrings.nonStrongAuthTimeout( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() } else if (isTrustUsuallyManaged && flags.someAuthRequiredAfterUserRequest) { - trustAgentDisabled(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value) + BouncerMessageStrings.trustAgentDisabled( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() } else if (isTrustUsuallyManaged && flags.someAuthRequiredAfterTrustAgentExpired) { - trustAgentDisabled(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value) + BouncerMessageStrings.trustAgentDisabled( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() } else if (trustOrBiometricsAvailable && flags.isInUserLockdown) { - authRequiredAfterUserLockdown(currentSecurityMode) + BouncerMessageStrings.authRequiredAfterUserLockdown( + currentSecurityMode.toAuthModel() + ) + .toMessage() } else { defaultMessage } @@ -244,7 +245,11 @@ constructor( override fun onTick(millisUntilFinished: Long) { val secondsRemaining = (millisUntilFinished / 1000.0).roundToInt() - val message = primaryAuthLockedOut(currentSecurityMode) + val message = + BouncerMessageStrings.primaryAuthLockedOut( + currentSecurityMode.toAuthModel() + ) + .toMessage() message.message?.animate = false message.message?.formatterArgs = mutableMapOf<String, Any>(Pair("count", secondsRemaining)) @@ -258,7 +263,11 @@ constructor( if (!Flags.revampedBouncerMessages()) return repository.setMessage( - incorrectSecurityInput(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value) + BouncerMessageStrings.incorrectSecurityInput( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() ) } @@ -285,7 +294,12 @@ constructor( } private val defaultMessage: BouncerMessageModel - get() = defaultMessage(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value) + get() = + BouncerMessageStrings.defaultMessage( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() fun onPrimaryBouncerUserInput() { if (!Flags.revampedBouncerMessages()) return @@ -354,283 +368,35 @@ private fun defaultMessage( return BouncerMessageModel( message = Message( - messageResId = defaultMessage(securityMode, fpAuthIsAllowed).message?.messageResId, + messageResId = + BouncerMessageStrings.defaultMessage( + securityMode.toAuthModel(), + fpAuthIsAllowed + ) + .toMessage() + .message + ?.messageResId, animate = false ), secondaryMessage = Message(message = secondaryMessage, animate = false) ) } -private fun defaultMessage( - securityMode: SecurityMode, - fpAuthIsAllowed: Boolean -): BouncerMessageModel { - return if (fpAuthIsAllowed) { - defaultMessageWithFingerprint(securityMode) - } else - when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0) - SecurityMode.Password -> Pair(keyguard_enter_password, 0) - SecurityMode.PIN -> Pair(keyguard_enter_pin, 0) - else -> Pair(0, 0) - }.toMessage() -} - -private fun defaultMessageWithFingerprint(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, 0) - SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, 0) - SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, 0) - else -> Pair(0, 0) - }.toMessage() -} - -private fun incorrectSecurityInput( - securityMode: SecurityMode, - fpAuthIsAllowed: Boolean -): BouncerMessageModel { - return if (fpAuthIsAllowed) { - incorrectSecurityInputWithFingerprint(securityMode) - } else - when (securityMode) { - SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, 0) - SecurityMode.Password -> Pair(kg_wrong_password_try_again, 0) - SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, 0) - else -> Pair(0, 0) - }.toMessage() -} - -private fun incorrectSecurityInputWithFingerprint(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, kg_wrong_input_try_fp_suggestion) - SecurityMode.Password -> Pair(kg_wrong_password_try_again, kg_wrong_input_try_fp_suggestion) - SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, kg_wrong_input_try_fp_suggestion) - else -> Pair(0, 0) - }.toMessage() -} - -private fun incorrectFingerprintInput(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pattern) - SecurityMode.Password -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_password) - SecurityMode.PIN -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pin) - else -> Pair(0, 0) - }.toMessage() -} - -private fun incorrectFaceInput( - securityMode: SecurityMode, - fpAuthIsAllowed: Boolean -): BouncerMessageModel { - return if (fpAuthIsAllowed) incorrectFaceInputWithFingerprintAllowed(securityMode) - else - when (securityMode) { - SecurityMode.Pattern -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pattern) - SecurityMode.Password -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_password) - SecurityMode.PIN -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pin) - else -> Pair(0, 0) - }.toMessage() -} - -private fun incorrectFaceInputWithFingerprintAllowed( - securityMode: SecurityMode -): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, bouncer_face_not_recognized) - SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, bouncer_face_not_recognized) - SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, bouncer_face_not_recognized) - else -> Pair(0, 0) - }.toMessage() -} - -private fun biometricLockout(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_bio_too_many_attempts_pin) - else -> Pair(0, 0) - }.toMessage() -} - -private fun authRequiredAfterReboot(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_reason_restart_pattern) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_reason_restart_password) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin) - else -> Pair(0, 0) - }.toMessage() -} - -private fun authRequiredAfterAdminLockdown(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_dpm_lock) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_dpm_lock) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock) - else -> Pair(0, 0) - }.toMessage() -} - -private fun authRequiredAfterAdaptiveAuthRequest( - securityMode: SecurityMode, - fpAuthIsAllowed: Boolean -): BouncerMessageModel { - return if (fpAuthIsAllowed) authRequiredAfterAdaptiveAuthRequestFingerprintAllowed(securityMode) - else - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_adaptive_auth_lock) - SecurityMode.Password -> - Pair(keyguard_enter_password, kg_prompt_after_adaptive_auth_lock) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_adaptive_auth_lock) - else -> Pair(0, 0) - }.toMessage() -} - -private fun authRequiredAfterAdaptiveAuthRequestFingerprintAllowed( - securityMode: SecurityMode -): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> - Pair(kg_unlock_with_pattern_or_fp, kg_prompt_after_adaptive_auth_lock) - SecurityMode.Password -> - Pair(kg_unlock_with_password_or_fp, kg_prompt_after_adaptive_auth_lock) - SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_prompt_after_adaptive_auth_lock) - else -> Pair(0, 0) - }.toMessage() -} - -private fun authRequiredAfterUserLockdown(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_user_lockdown_pattern) - SecurityMode.Password -> - Pair(keyguard_enter_password, kg_prompt_after_user_lockdown_password) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin) - else -> Pair(0, 0) - }.toMessage() -} - -private fun authRequiredForUnattendedUpdate(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_unattended_update) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_unattended_update) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_unattended_update) - else -> Pair(0, 0) - }.toMessage() -} - -private fun authRequiredForMainlineUpdate(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_update_pattern) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_update_password) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_update_pin) - else -> Pair(0, 0) - }.toMessage() -} - -private fun authRequiredAfterPrimaryAuthTimeout(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_pattern_auth_timeout) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_password_auth_timeout) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout) - else -> Pair(0, 0) - }.toMessage() -} - -private fun nonStrongAuthTimeout( - securityMode: SecurityMode, - fpAuthIsAllowed: Boolean -): BouncerMessageModel { - return if (fpAuthIsAllowed) { - nonStrongAuthTimeoutWithFingerprintAllowed(securityMode) - } else - when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_auth_timeout) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_auth_timeout) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_auth_timeout) - else -> Pair(0, 0) - }.toMessage() -} - -fun nonStrongAuthTimeoutWithFingerprintAllowed(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_prompt_auth_timeout) - SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_prompt_auth_timeout) - SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_prompt_auth_timeout) - else -> Pair(0, 0) - }.toMessage() -} - -private fun faceLockedOut( - securityMode: SecurityMode, - fpAuthIsAllowed: Boolean -): BouncerMessageModel { - return if (fpAuthIsAllowed) faceLockedOutButFingerprintAvailable(securityMode) - else - when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_face_locked_out) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_face_locked_out) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_face_locked_out) - else -> Pair(0, 0) - }.toMessage() -} - -private fun faceLockedOutButFingerprintAvailable(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_face_locked_out) - SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_face_locked_out) - SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_face_locked_out) - else -> Pair(0, 0) - }.toMessage() -} - -private fun class3AuthLockedOut(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_bio_too_many_attempts_pin) - else -> Pair(0, 0) - }.toMessage() -} - -private fun trustAgentDisabled( - securityMode: SecurityMode, - fpAuthIsAllowed: Boolean -): BouncerMessageModel { - return if (fpAuthIsAllowed) trustAgentDisabledWithFingerprintAllowed(securityMode) - else - when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_trust_agent_disabled) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_trust_agent_disabled) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_trust_agent_disabled) - else -> Pair(0, 0) - }.toMessage() -} - -private fun trustAgentDisabledWithFingerprintAllowed( - securityMode: SecurityMode -): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_trust_agent_disabled) - SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_trust_agent_disabled) - SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_trust_agent_disabled) - else -> Pair(0, 0) - }.toMessage() -} - -private fun primaryAuthLockedOut(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> - Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pattern) - SecurityMode.Password -> - Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_password) - SecurityMode.PIN -> - Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pin) - else -> Pair(0, 0) - }.toMessage() -} - private fun Pair<Int, Int>.toMessage(): BouncerMessageModel { return BouncerMessageModel( message = Message(messageResId = this.first, animate = false), secondaryMessage = Message(messageResId = this.second, animate = false) ) } + +private fun SecurityMode.toAuthModel(): AuthenticationMethodModel { + return when (this) { + SecurityMode.Invalid -> AuthenticationMethodModel.None + SecurityMode.None -> AuthenticationMethodModel.None + SecurityMode.Pattern -> AuthenticationMethodModel.Pattern + SecurityMode.Password -> AuthenticationMethodModel.Password + SecurityMode.PIN -> AuthenticationMethodModel.Pin + SecurityMode.SimPin -> AuthenticationMethodModel.Sim + SecurityMode.SimPuk -> AuthenticationMethodModel.Sim + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt new file mode 100644 index 000000000000..cb12ce50dd23 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bouncer.shared.model + +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin +import com.android.systemui.res.R + +typealias BouncerMessagePair = Pair<Int, Int> + +val BouncerMessagePair.primaryMessage: Int + get() = this.first + +val BouncerMessagePair.secondaryMessage: Int + get() = this.second + +object BouncerMessageStrings { + private val EmptyMessage = Pair(0, 0) + + fun defaultMessage( + securityMode: AuthenticationMethodModel, + fpAuthIsAllowed: Boolean + ): BouncerMessagePair { + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), 0) + Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), 0) + Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), 0) + else -> EmptyMessage + } + } + + fun incorrectSecurityInput( + securityMode: AuthenticationMethodModel, + fpAuthIsAllowed: Boolean + ): BouncerMessagePair { + val secondaryMessage = incorrectSecurityInputSecondaryMessage(fpAuthIsAllowed) + return when (securityMode) { + Pattern -> Pair(R.string.kg_wrong_pattern_try_again, secondaryMessage) + Password -> Pair(R.string.kg_wrong_password_try_again, secondaryMessage) + Pin -> Pair(R.string.kg_wrong_pin_try_again, secondaryMessage) + else -> EmptyMessage + } + } + + private fun incorrectSecurityInputSecondaryMessage(fpAuthIsAllowed: Boolean): Int { + return if (fpAuthIsAllowed) R.string.kg_wrong_input_try_fp_suggestion else 0 + } + + fun incorrectFingerprintInput(securityMode: AuthenticationMethodModel): BouncerMessagePair { + val primaryMessage = R.string.kg_fp_not_recognized + return when (securityMode) { + Pattern -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pattern) + Password -> Pair(primaryMessage, R.string.kg_bio_try_again_or_password) + Pin -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pin) + else -> EmptyMessage + } + } + + fun incorrectFaceInput( + securityMode: AuthenticationMethodModel, + fpAuthIsAllowed: Boolean + ): BouncerMessagePair { + return if (fpAuthIsAllowed) incorrectFaceInputWithFingerprintAllowed(securityMode) + else { + val primaryMessage = R.string.bouncer_face_not_recognized + when (securityMode) { + Pattern -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pattern) + Password -> Pair(primaryMessage, R.string.kg_bio_try_again_or_password) + Pin -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pin) + else -> EmptyMessage + } + } + } + + private fun incorrectFaceInputWithFingerprintAllowed( + securityMode: AuthenticationMethodModel + ): BouncerMessagePair { + val secondaryMsg = R.string.bouncer_face_not_recognized + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(true), secondaryMsg) + Password -> Pair(passwordDefaultMessage(true), secondaryMsg) + Pin -> Pair(pinDefaultMessage(true), secondaryMsg) + else -> EmptyMessage + } + } + + fun authRequiredAfterReboot(securityMode: AuthenticationMethodModel): BouncerMessagePair { + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_reason_restart_pattern) + Password -> + Pair(passwordDefaultMessage(false), R.string.kg_prompt_reason_restart_password) + Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_reason_restart_pin) + else -> EmptyMessage + } + } + + fun authRequiredAfterAdminLockdown( + securityMode: AuthenticationMethodModel + ): BouncerMessagePair { + val secondaryMsg = R.string.kg_prompt_after_dpm_lock + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(false), secondaryMsg) + Password -> Pair(passwordDefaultMessage(false), secondaryMsg) + Pin -> Pair(pinDefaultMessage(false), secondaryMsg) + else -> EmptyMessage + } + } + + fun authRequiredAfterAdaptiveAuthRequest( + securityMode: AuthenticationMethodModel, + fpAuthIsAllowed: Boolean + ): BouncerMessagePair { + val secondaryMsg = R.string.kg_prompt_after_adaptive_auth_lock + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg) + Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg) + Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg) + else -> EmptyMessage + } + } + + fun authRequiredAfterUserLockdown(securityMode: AuthenticationMethodModel): BouncerMessagePair { + return when (securityMode) { + Pattern -> + Pair(patternDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_pattern) + Password -> + Pair(passwordDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_password) + Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_pin) + else -> EmptyMessage + } + } + + fun authRequiredForUnattendedUpdate( + securityMode: AuthenticationMethodModel + ): BouncerMessagePair { + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_added_security_pattern) + Password -> + Pair(passwordDefaultMessage(false), R.string.kg_prompt_added_security_password) + Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_added_security_pin) + else -> EmptyMessage + } + } + + fun authRequiredForMainlineUpdate(securityMode: AuthenticationMethodModel): BouncerMessagePair { + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_after_update_pattern) + Password -> + Pair(passwordDefaultMessage(false), R.string.kg_prompt_after_update_password) + Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_after_update_pin) + else -> EmptyMessage + } + } + + fun authRequiredAfterPrimaryAuthTimeout( + securityMode: AuthenticationMethodModel + ): BouncerMessagePair { + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_pattern_auth_timeout) + Password -> + Pair(passwordDefaultMessage(false), R.string.kg_prompt_password_auth_timeout) + Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_pin_auth_timeout) + else -> EmptyMessage + } + } + + fun nonStrongAuthTimeout( + securityMode: AuthenticationMethodModel, + fpAuthIsAllowed: Boolean + ): BouncerMessagePair { + val secondaryMsg = R.string.kg_prompt_auth_timeout + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg) + Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg) + Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg) + else -> EmptyMessage + } + } + + fun faceLockedOut( + securityMode: AuthenticationMethodModel, + fpAuthIsAllowed: Boolean + ): BouncerMessagePair { + val secondaryMsg = R.string.kg_face_locked_out + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg) + Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg) + Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg) + else -> EmptyMessage + } + } + + fun class3AuthLockedOut(securityMode: AuthenticationMethodModel): BouncerMessagePair { + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(false), R.string.kg_bio_too_many_attempts_pattern) + Password -> + Pair(passwordDefaultMessage(false), R.string.kg_bio_too_many_attempts_password) + Pin -> Pair(pinDefaultMessage(false), R.string.kg_bio_too_many_attempts_pin) + else -> EmptyMessage + } + } + + fun trustAgentDisabled( + securityMode: AuthenticationMethodModel, + fpAuthIsAllowed: Boolean + ): BouncerMessagePair { + val secondaryMsg = R.string.kg_trust_agent_disabled + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg) + Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg) + Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg) + else -> EmptyMessage + } + } + + fun primaryAuthLockedOut(securityMode: AuthenticationMethodModel): BouncerMessagePair { + return when (securityMode) { + Pattern -> + Pair( + R.string.kg_too_many_failed_attempts_countdown, + R.string.kg_primary_auth_locked_out_pattern + ) + Password -> + Pair( + R.string.kg_too_many_failed_attempts_countdown, + R.string.kg_primary_auth_locked_out_password + ) + Pin -> + Pair( + R.string.kg_too_many_failed_attempts_countdown, + R.string.kg_primary_auth_locked_out_pin + ) + else -> EmptyMessage + } + } + + private fun patternDefaultMessage(fingerprintAllowed: Boolean): Int { + return if (fingerprintAllowed) R.string.kg_unlock_with_pattern_or_fp + else R.string.keyguard_enter_pattern + } + + private fun pinDefaultMessage(fingerprintAllowed: Boolean): Int { + return if (fingerprintAllowed) R.string.kg_unlock_with_pin_or_fp + else R.string.keyguard_enter_pin + } + + private fun passwordDefaultMessage(fingerprintAllowed: Boolean): Int { + return if (fingerprintAllowed) R.string.kg_unlock_with_password_or_fp + else R.string.keyguard_enter_password + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt new file mode 100644 index 000000000000..9e7fb4e73a29 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal + +import android.annotation.SuppressLint +import android.app.DreamManager +import com.android.systemui.CoreStartable +import com.android.systemui.Flags.communalHub +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.util.kotlin.Utils.Companion.sample +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +/** + * A [CoreStartable] responsible for automatically starting the dream when the communal hub is + * shown, to support the user swiping away the hub to enter the dream. + */ +@SysUISingleton +class CommunalDreamStartable +@Inject +constructor( + private val powerInteractor: PowerInteractor, + private val keyguardInteractor: KeyguardInteractor, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, + private val dreamManager: DreamManager, + @Background private val bgScope: CoroutineScope, +) : CoreStartable { + @SuppressLint("MissingPermission") + override fun start() { + if (!communalHub()) { + return + } + + // Restart the dream underneath the hub in order to support the ability to swipe + // away the hub to enter the dream. + keyguardTransitionInteractor.finishedKeyguardState + .sample(powerInteractor.isAwake, keyguardInteractor.isDreaming) + .onEach { (finishedState, isAwake, dreaming) -> + if ( + finishedState == KeyguardState.GLANCEABLE_HUB && + !dreaming && + dreamManager.canStartDreaming(isAwake) + ) { + dreamManager.startDream() + } + } + .launchIn(bgScope) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index fa10d85caf18..940b48cc94c9 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -19,10 +19,13 @@ package com.android.systemui.communal.domain.interactor import android.app.smartspace.SmartspaceTarget import android.content.ComponentName import android.content.Intent +import android.content.IntentFilter import android.os.UserHandle +import android.os.UserManager import android.provider.Settings import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.communal.data.repository.CommunalMediaRepository import com.android.systemui.communal.data.repository.CommunalPrefsRepository import com.android.systemui.communal.data.repository.CommunalRepository @@ -56,6 +59,7 @@ import com.android.systemui.smartspace.data.repository.SmartspaceRepository import com.android.systemui.util.kotlin.BooleanFlowOperators.and import com.android.systemui.util.kotlin.BooleanFlowOperators.not import com.android.systemui.util.kotlin.BooleanFlowOperators.or +import com.android.systemui.util.kotlin.emitOnStart import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -81,6 +85,7 @@ class CommunalInteractor @Inject constructor( @Application applicationScope: CoroutineScope, + broadcastDispatcher: BroadcastDispatcher, private val communalRepository: CommunalRepository, private val widgetRepository: CommunalWidgetRepository, private val communalPrefsRepository: CommunalPrefsRepository, @@ -92,6 +97,7 @@ constructor( private val editWidgetsActivityStarter: EditWidgetsActivityStarter, private val userTracker: UserTracker, private val activityStarter: ActivityStarter, + private val userManager: UserManager, sceneInteractor: SceneInteractor, sceneContainerFlags: SceneContainerFlags, @CommunalLog logBuffer: LogBuffer, @@ -288,6 +294,33 @@ constructor( fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) = widgetRepository.updateWidgetOrder(widgetIdToPriorityMap) + /** Request to unpause work profile that is currently in quiet mode. */ + fun unpauseWorkProfile() { + userTracker.userProfiles + .find { it.isManagedProfile } + ?.userHandle + ?.let { userHandle -> + userManager.requestQuietModeEnabled(/* enableQuietMode */ false, userHandle) + } + } + + /** Returns true if work profile is in quiet mode (disabled) for user handle. */ + private fun isQuietModeEnabled(userHandle: UserHandle): Boolean = + userManager.isManagedProfile(userHandle.identifier) && + userManager.isQuietModeEnabled(userHandle) + + /** Emits whenever a work profile pause or unpause broadcast is received. */ + private val updateOnWorkProfileBroadcastReceived: Flow<Unit> = + broadcastDispatcher + .broadcastFlow( + filter = + IntentFilter().apply { + addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE) + addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE) + }, + ) + .emitOnStart() + /** All widgets present in db. */ val communalWidgets: Flow<List<CommunalWidgetContentModel>> = isCommunalAvailable.flatMapLatest { available -> @@ -298,8 +331,9 @@ constructor( val widgetContent: Flow<List<WidgetContent>> = combine( widgetRepository.communalWidgets.map { filterWidgetsByExistingUsers(it) }, - communalSettingsInteractor.communalWidgetCategories - ) { widgets, allowedCategories -> + communalSettingsInteractor.communalWidgetCategories, + updateOnWorkProfileBroadcastReceived, + ) { widgets, allowedCategories, _ -> widgets.map { widget -> if (widget.providerInfo.widgetCategory and allowedCategories != 0) { // At least one category this widget specified is allowed, so show it @@ -307,6 +341,7 @@ constructor( appWidgetId = widget.appWidgetId, providerInfo = widget.providerInfo, appWidgetHost = appWidgetHost, + inQuietMode = isQuietModeEnabled(widget.providerInfo.profile) ) } else { WidgetContent.DisabledWidget( diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt index 12576d466823..5fabd3c9bed8 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt @@ -51,6 +51,7 @@ sealed interface CommunalContentModel { override val appWidgetId: Int, override val providerInfo: AppWidgetProviderInfo, val appWidgetHost: CommunalAppWidgetHost, + val inQuietMode: Boolean, ) : WidgetContent { override val key = KEY.widget(appWidgetId) // Widget size is always half. diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt index feb506aaa527..85f3c202f10f 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt @@ -72,6 +72,8 @@ abstract class BaseCommunalViewModel( open fun onOpenEnableWidgetDialog() {} + open fun onOpenEnableWorkProfileDialog() {} + /** A list of all the communal content to be displayed in the communal hub. */ abstract val communalContent: Flow<List<CommunalContentModel>> diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index 0c332f52ba06..6e69ed7c3267 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -90,6 +90,11 @@ constructor( private val _isEnableWidgetDialogShowing: MutableStateFlow<Boolean> = MutableStateFlow(false) val isEnableWidgetDialogShowing: Flow<Boolean> = _isEnableWidgetDialogShowing.asStateFlow() + private val _isEnableWorkProfileDialogShowing: MutableStateFlow<Boolean> = + MutableStateFlow(false) + val isEnableWorkProfileDialogShowing: Flow<Boolean> = + _isEnableWorkProfileDialogShowing.asStateFlow() + /** Whether touches should be disabled in communal */ val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded) @@ -136,10 +141,27 @@ constructor( setIsEnableWidgetDialogShowing(false) } + override fun onOpenEnableWorkProfileDialog() { + setIsEnableWorkProfileDialogShowing(true) + } + + fun onEnableWorkProfileDialogConfirm() { + communalInteractor.unpauseWorkProfile() + setIsEnableWorkProfileDialogShowing(false) + } + + fun onEnableWorkProfileDialogCancel() { + setIsEnableWorkProfileDialogShowing(false) + } + private fun setIsEnableWidgetDialogShowing(isVisible: Boolean) { _isEnableWidgetDialogShowing.value = isVisible } + private fun setIsEnableWorkProfileDialogShowing(isVisible: Boolean) { + _isEnableWorkProfileDialogShowing.value = isVisible + } + private fun setPopupOnDismissCtaVisibility(isVisible: Boolean) { _isPopupOnDismissCtaShowing.value = isVisible } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index a3d6ad456e54..21ee5bd92328 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -24,6 +24,7 @@ import com.android.systemui.accessibility.Magnification import com.android.systemui.back.domain.interactor.BackActionInteractor import com.android.systemui.biometrics.BiometricNotificationService import com.android.systemui.clipboardoverlay.ClipboardListener +import com.android.systemui.communal.CommunalDreamStartable import com.android.systemui.communal.CommunalSceneStartable import com.android.systemui.communal.log.CommunalLoggerStartable import com.android.systemui.communal.widgets.CommunalAppWidgetHostStartable @@ -328,6 +329,11 @@ abstract class SystemUICoreStartableModule { @Binds @IntoMap + @ClassKey(CommunalDreamStartable::class) + abstract fun bindCommunalDreamStartable(impl: CommunalDreamStartable): CoreStartable + + @Binds + @IntoMap @ClassKey(CommunalAppWidgetHostStartable::class) abstract fun bindCommunalAppWidgetHostStartable( impl: CommunalAppWidgetHostStartable diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt index b97bace9584f..f860893f800b 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt @@ -33,7 +33,7 @@ import com.android.systemui.complication.ComplicationLayoutParams.POSITION_BOTTO import com.android.systemui.complication.ComplicationLayoutParams.POSITION_TOP import com.android.systemui.complication.ComplicationLayoutParams.Position import com.android.systemui.dreams.dagger.DreamOverlayModule -import com.android.systemui.dreams.ui.viewmodel.DreamOverlayViewModel +import com.android.systemui.dreams.ui.viewmodel.DreamViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger @@ -53,7 +53,7 @@ constructor( private val mStatusBarViewController: DreamOverlayStatusBarViewController, private val mOverlayStateController: DreamOverlayStateController, @Named(DreamOverlayModule.DREAM_BLUR_RADIUS) private val mDreamBlurRadius: Int, - private val dreamOverlayViewModel: DreamOverlayViewModel, + private val dreamViewModel: DreamViewModel, @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION) private val mDreamInBlurAnimDurationMs: Long, @Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION) @@ -87,7 +87,7 @@ constructor( view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { launch { - dreamOverlayViewModel.dreamOverlayTranslationY.collect { px -> + dreamViewModel.dreamOverlayTranslationY.collect { px -> ComplicationLayoutParams.iteratePositions( { position: Int -> setElementsTranslationYAtPosition(px, position) }, POSITION_TOP or POSITION_BOTTOM @@ -96,7 +96,7 @@ constructor( } launch { - dreamOverlayViewModel.dreamOverlayTranslationX.collect { px -> + dreamViewModel.dreamOverlayTranslationX.collect { px -> ComplicationLayoutParams.iteratePositions( { position: Int -> setElementsTranslationXAtPosition(px, position) }, POSITION_TOP or POSITION_BOTTOM @@ -105,7 +105,7 @@ constructor( } launch { - dreamOverlayViewModel.dreamOverlayAlpha.collect { alpha -> + dreamViewModel.dreamOverlayAlpha.collect { alpha -> ComplicationLayoutParams.iteratePositions( { position: Int -> setElementsAlphaAtPosition( @@ -120,7 +120,7 @@ constructor( } launch { - dreamOverlayViewModel.transitionEnded.collect { _ -> + dreamViewModel.transitionEnded.collect { _ -> mOverlayStateController.setExitAnimationsRunning(false) } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt deleted file mode 100644 index bd99f4b8230e..000000000000 --- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.dreams.ui.viewmodel - -import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel -import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel -import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel -import com.android.systemui.res.R -import javax.inject.Inject -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.merge - -@OptIn(ExperimentalCoroutinesApi::class) -@SysUISingleton -class DreamOverlayViewModel -@Inject -constructor( - configurationInteractor: ConfigurationInteractor, - toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel, - fromGlanceableHubTransitionInteractor: GlanceableHubToDreamingTransitionViewModel, - private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, -) { - - val dreamOverlayTranslationX: Flow<Float> = - merge( - toGlanceableHubTransitionViewModel.dreamOverlayTranslationX, - fromGlanceableHubTransitionInteractor.dreamOverlayTranslationX, - ) - - val dreamOverlayTranslationY: Flow<Float> = - configurationInteractor - .dimensionPixelSize(R.dimen.dream_overlay_exit_y_offset) - .flatMapLatest { px: Int -> - toLockscreenTransitionViewModel.dreamOverlayTranslationY(px) - } - - val dreamOverlayAlpha: Flow<Float> = - merge( - toLockscreenTransitionViewModel.dreamOverlayAlpha, - toGlanceableHubTransitionViewModel.dreamOverlayAlpha, - fromGlanceableHubTransitionInteractor.dreamOverlayAlpha, - ) - - val transitionEnded = toLockscreenTransitionViewModel.transitionEnded -} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt new file mode 100644 index 000000000000..0cb57fb937d2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.ui.viewmodel + +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor +import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dock.DockManager +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel +import com.android.systemui.res.R +import com.android.systemui.settings.UserTracker +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.merge + +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class DreamViewModel +@Inject +constructor( + configurationInteractor: ConfigurationInteractor, + keyguardTransitionInteractor: KeyguardTransitionInteractor, + fromGlanceableHubTransitionInteractor: GlanceableHubToDreamingTransitionViewModel, + private val toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel, + private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, + private val dockManager: DockManager, + private val communalInteractor: CommunalInteractor, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val userTracker: UserTracker, +) { + + fun startTransitionFromDream() { + val showGlanceableHub = + dockManager.isDocked && + communalInteractor.isCommunalEnabled.value && + !keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId) + if (showGlanceableHub) { + toGlanceableHubTransitionViewModel.startTransition() + communalInteractor.onSceneChanged(CommunalScenes.Communal) + } else { + toLockscreenTransitionViewModel.startTransition() + } + } + + val dreamOverlayTranslationX: Flow<Float> = + merge( + toGlanceableHubTransitionViewModel.dreamOverlayTranslationX, + fromGlanceableHubTransitionInteractor.dreamOverlayTranslationX, + ) + .distinctUntilChanged() + + val dreamOverlayTranslationY: Flow<Float> = + configurationInteractor + .dimensionPixelSize(R.dimen.dream_overlay_exit_y_offset) + .flatMapLatest { px: Int -> + toLockscreenTransitionViewModel.dreamOverlayTranslationY(px) + } + + val dreamAlpha: Flow<Float> = + merge( + toLockscreenTransitionViewModel.dreamOverlayAlpha, + toGlanceableHubTransitionViewModel.dreamAlpha, + ) + .distinctUntilChanged() + + val dreamOverlayAlpha: Flow<Float> = + merge( + toLockscreenTransitionViewModel.dreamOverlayAlpha, + toGlanceableHubTransitionViewModel.dreamOverlayAlpha, + fromGlanceableHubTransitionInteractor.dreamOverlayAlpha, + ) + .distinctUntilChanged() + + val transitionEnded = + keyguardTransitionInteractor.fromDreamingTransition.filter { step -> + step.transitionState == TransitionState.FINISHED || + step.transitionState == TransitionState.CANCELED + } +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 6bb846491224..a199fea0253f 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -76,23 +76,6 @@ object Flags { val NOTIFICATION_MEMORY_LOGGING_ENABLED = releasedFlag("notification_memory_logging_enabled") - // TODO(b/260335638): Tracking Bug - @JvmField - val NOTIFICATION_INLINE_REPLY_ANIMATION = releasedFlag("notification_inline_reply_animation") - - // TODO(b/288326013): Tracking Bug - @JvmField - val NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION = - unreleasedFlag("notification_async_hybrid_view_inflation", teamfood = false) - - @JvmField - val ANIMATED_NOTIFICATION_SHADE_INSETS = - releasedFlag("animated_notification_shade_insets") - - // TODO(b/268005230): Tracking Bug - @JvmField - val SENSITIVE_REVEAL_ANIM = releasedFlag("sensitive_reveal_anim") - // TODO(b/280783617): Tracking Bug @Keep @JvmField diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index 106fdf1fbcbe..5565ee295786 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -42,6 +42,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.shared.ComposeLockscreen import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder @@ -107,6 +108,7 @@ constructor( private val lockscreenContentViewModel: LockscreenContentViewModel, private val lockscreenSceneBlueprintsLazy: Lazy<Set<LockscreenSceneBlueprint>>, private val keyguardBlueprintViewBinder: KeyguardBlueprintViewBinder, + private val clockInteractor: KeyguardClockInteractor, ) : CoreStartable { private var rootViewHandle: DisposableHandle? = null @@ -220,7 +222,11 @@ constructor( blueprints.mapNotNull { it as? ComposableLockscreenSceneBlueprint }.toSet() return ComposeView(context).apply { setContent { - LockscreenContent(viewModel = viewModel, blueprints = sceneBlueprints) + LockscreenContent( + viewModel = viewModel, + blueprints = sceneBlueprints, + clockInteractor = clockInteractor + ) .Content(modifier = Modifier.fillMaxSize()) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index a37397db81f8..43a8b40a3150 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -140,13 +140,13 @@ import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.dreams.ui.viewmodel.DreamViewModel; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.SystemPropertiesHelper; import com.android.systemui.keyguard.dagger.KeyguardModule; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.shared.model.TransitionStep; -import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.log.SessionTracker; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -1229,9 +1229,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, if (isDream) { initAlphaForAnimationTargets(wallpapers); - getRemoteSurfaceAlphaApplier().accept(0.0f); - mDreamingToLockscreenTransitionViewModel.get() - .startTransition(); + mDreamViewModel.get().startTransitionFromDream(); mUnoccludeFromDreamFinishedCallback = finishedCallback; return; } @@ -1359,8 +1357,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private final UiEventLogger mUiEventLogger; private final SessionTracker mSessionTracker; private final CoroutineDispatcher mMainDispatcher; - private final Lazy<DreamingToLockscreenTransitionViewModel> - mDreamingToLockscreenTransitionViewModel; + private final Lazy<DreamViewModel> mDreamViewModel; private RemoteAnimationTarget mRemoteAnimationTarget; private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager; @@ -1409,7 +1406,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, SystemSettings systemSettings, SystemClock systemClock, @Main CoroutineDispatcher mainDispatcher, - Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel, + Lazy<DreamViewModel> dreamViewModel, SystemPropertiesHelper systemPropertiesHelper, Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager, SelectedUserInteractor selectedUserInteractor, @@ -1480,7 +1477,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mUiEventLogger = uiEventLogger; mSessionTracker = sessionTracker; - mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel; + mDreamViewModel = dreamViewModel; mWmLockscreenVisibilityManager = wmLockscreenVisibilityManager; mMainDispatcher = mainDispatcher; @@ -1611,9 +1608,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, ViewRootImpl viewRootImpl = mKeyguardViewControllerLazy.get().getViewRootImpl(); if (viewRootImpl != null) { - DreamingToLockscreenTransitionViewModel viewModel = - mDreamingToLockscreenTransitionViewModel.get(); - collectFlow(viewRootImpl.getView(), viewModel.getDreamOverlayAlpha(), + final DreamViewModel viewModel = mDreamViewModel.get(); + collectFlow(viewRootImpl.getView(), viewModel.getDreamAlpha(), getRemoteSurfaceAlphaApplier(), mMainDispatcher); collectFlow(viewRootImpl.getView(), viewModel.getTransitionEnded(), getFinishedCallbackConsumer(), mMainDispatcher); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 5306645bf69f..a243b8eec264 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -43,6 +43,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.dreams.ui.viewmodel.DreamViewModel; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.SystemPropertiesHelper; @@ -59,7 +60,6 @@ import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionMo import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger; import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl; import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransitionModule; -import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.log.SessionTracker; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.settings.UserTracker; @@ -160,7 +160,7 @@ public interface KeyguardModule { SystemSettings systemSettings, SystemClock systemClock, @Main CoroutineDispatcher mainDispatcher, - Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel, + Lazy<DreamViewModel> dreamViewModel, SystemPropertiesHelper systemPropertiesHelper, Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager, SelectedUserInteractor selectedUserInteractor, @@ -207,7 +207,7 @@ public interface KeyguardModule { systemSettings, systemClock, mainDispatcher, - dreamingToLockscreenTransitionViewModel, + dreamViewModel, systemPropertiesHelper, wmLockscreenVisibilityManager, selectedUserInteractor, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index 31371384e338..9a6088de110e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -35,6 +35,7 @@ import com.android.systemui.util.kotlin.Utils.Companion.toTriple import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.combine @@ -97,6 +98,18 @@ constructor( } } + fun startToGlanceableHubTransition() { + scope.launch { + KeyguardWmStateRefactor.isUnexpectedlyInLegacyMode() + if ( + transitionInteractor.startedKeyguardState.replayCache.last() == + KeyguardState.DREAMING + ) { + startTransitionTo(KeyguardState.GLANCEABLE_HUB) + } + } + } + private fun listenForDreamingToOccluded() { if (KeyguardWmStateRefactor.isEnabled) { scope.launch { @@ -205,14 +218,18 @@ constructor( return ValueAnimator().apply { interpolator = Interpolators.LINEAR duration = - if (toState == KeyguardState.LOCKSCREEN) TO_LOCKSCREEN_DURATION.inWholeMilliseconds - else DEFAULT_DURATION.inWholeMilliseconds + when (toState) { + KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION + KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION + else -> DEFAULT_DURATION + }.inWholeMilliseconds } } companion object { const val TAG = "FromDreamingTransitionInteractor" private val DEFAULT_DURATION = 500.milliseconds + val TO_GLANCEABLE_HUB_DURATION = 1.seconds val TO_LOCKSCREEN_DURATION = 1167.milliseconds } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt index 744301019dfc..197221a7b5b3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt @@ -21,7 +21,6 @@ import com.android.app.animation.Interpolators import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalTransitionProgress import com.android.systemui.communal.shared.model.CommunalScenes -import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionInfo @@ -29,13 +28,10 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.util.kotlin.sample import java.util.UUID import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.flow.flowOn class GlanceableHubTransitions @Inject constructor( - @Background private val bgDispatcher: CoroutineDispatcher, private val transitionInteractor: KeyguardTransitionInteractor, private val transitionRepository: KeyguardTransitionRepository, private val communalInteractor: CommunalInteractor, @@ -64,16 +60,16 @@ constructor( communalInteractor .transitionProgressToScene(toScene) .sample( - transitionInteractor.startedKeyguardTransitionStep.flowOn(bgDispatcher), + transitionInteractor.startedKeyguardState, ::Pair, ) - .collect { (transitionProgress, lastStartedStep) -> + .collect { (transitionProgress, lastStartedState) -> val id = transitionId if (id == null) { // No transition started. if ( transitionProgress is CommunalTransitionProgress.Transition && - lastStartedStep.to == fromState + lastStartedState == fromState ) { transitionId = transitionRepository.startTransition( @@ -86,7 +82,7 @@ constructor( ) } } else { - if (lastStartedStep.to != toState) { + if (lastStartedState != toState) { return@collect } // An existing `id` means a transition is started, and calls to diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt index c64f277b519a..789e4fbd46c2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators.EMPHASIZED import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.res.R @@ -36,7 +37,9 @@ class DreamingToGlanceableHubTransitionViewModel constructor( animationFlow: KeyguardTransitionAnimationFlow, configurationInteractor: ConfigurationInteractor, + private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor, ) { + fun startTransition() = fromDreamingTransitionInteractor.startToGlanceableHubTransition() private val transitionAnimation = animationFlow.setup( @@ -58,6 +61,9 @@ constructor( ) } + // Keep the dream visible while the hub swipes in over the dream. + val dreamAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(1f) + val dreamOverlayAlpha: Flow<Float> = transitionAnimation.sharedFlow( duration = 167.milliseconds, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt index d3277cd295ac..f191aa7d7e4e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt @@ -20,16 +20,13 @@ import com.android.app.animation.Interpolators.EMPHASIZED import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState -import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.filter /** * Breaks down DREAMING->LOCKSCREEN transition into discrete steps for corresponding views to @@ -40,7 +37,6 @@ import kotlinx.coroutines.flow.filter class DreamingToLockscreenTransitionViewModel @Inject constructor( - keyguardTransitionInteractor: KeyguardTransitionInteractor, private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor, animationFlow: KeyguardTransitionAnimationFlow, ) : DeviceEntryIconTransition { @@ -53,12 +49,6 @@ constructor( to = KeyguardState.LOCKSCREEN, ) - val transitionEnded = - keyguardTransitionInteractor.fromDreamingTransition.filter { step -> - step.transitionState == TransitionState.FINISHED || - step.transitionState == TransitionState.CANCELED - } - /** Dream overlay y-translation on exit */ fun dreamOverlayTranslationY(translatePx: Int): Flow<Float> { return transitionAnimation.sharedFlow( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt index e5b596419efe..abf2372639fa 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt @@ -79,6 +79,8 @@ constructor( val notificationAlpha: Flow<Float> = keyguardAlpha + val shortcutsAlpha: Flow<Float> = keyguardAlpha + val notificationTranslationX: Flow<Float> = keyguardTranslationX.map { it.value }.filterNotNull() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt index f981fd50dc04..58c45c74815c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt @@ -36,6 +36,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @SysUISingleton @@ -67,7 +68,16 @@ constructor( .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = LARGE + initialValue = LARGE, + ) + + val isLargeClockVisible = + clockSize + .map { it == LARGE } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = false, ) val currentClock = keyguardClockInteractor.currentClock diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt index 4db942cc460c..c4383fc0857d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt @@ -52,6 +52,7 @@ constructor( occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel, offToLockscreenTransitionViewModel: OffToLockscreenTransitionViewModel, primaryBouncerToLockscreenTransitionViewModel: PrimaryBouncerToLockscreenTransitionViewModel, + glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel, lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel, lockscreenToDozingTransitionViewModel: LockscreenToDozingTransitionViewModel, lockscreenToDreamingHostedTransitionViewModel: LockscreenToDreamingHostedTransitionViewModel, @@ -59,6 +60,7 @@ constructor( lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel, lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel, lockscreenToPrimaryBouncerTransitionViewModel: LockscreenToPrimaryBouncerTransitionViewModel, + lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel, transitionInteractor: KeyguardTransitionInteractor, ) { @@ -110,6 +112,7 @@ constructor( occludedToLockscreenTransitionViewModel.shortcutsAlpha, offToLockscreenTransitionViewModel.shortcutsAlpha, primaryBouncerToLockscreenTransitionViewModel.shortcutsAlpha, + glanceableHubToLockscreenTransitionViewModel.shortcutsAlpha, ) /** alpha while fading the quick affordances in */ @@ -122,6 +125,7 @@ constructor( lockscreenToGoneTransitionViewModel.shortcutsAlpha, lockscreenToOccludedTransitionViewModel.shortcutsAlpha, lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha, + lockscreenToGlanceableHubTransitionViewModel.shortcutsAlpha, shadeExpansionAlpha, ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt index d89523d2de62..288ef3c52e21 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt @@ -23,6 +23,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -69,8 +70,8 @@ constructor( /** The key of the scene we should switch to when swiping down from the top edge. */ val downFromTopEdgeDestinationSceneKey: StateFlow<SceneKey?> = - shadeInteractor.isSplitShade - .map { isSplitShade -> Scenes.QuickSettings.takeUnless { isSplitShade } } + shadeInteractor.shadeMode + .map { shadeMode -> Scenes.QuickSettings.takeIf { shadeMode is ShadeMode.Single } } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt index 978e71e2a825..b7f7b06f6644 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt @@ -81,6 +81,8 @@ constructor( val notificationAlpha: Flow<Float> = keyguardAlpha + val shortcutsAlpha: Flow<Float> = keyguardAlpha + val notificationTranslationX: Flow<Float> = keyguardTranslationX.map { it.value }.filterNotNull() } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 91c86dff34ea..9d0ea5ebd925 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -19,6 +19,7 @@ import static android.view.InputDevice.SOURCE_MOUSE; import static android.view.InputDevice.SOURCE_TOUCHPAD; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION; +import static com.android.systemui.Flags.edgebackGestureHandlerGetRunningTasksBackground; import static com.android.systemui.classifier.Classifier.BACK_GESTURE; import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll; import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe; @@ -54,7 +55,6 @@ import android.view.ISystemGestureExclusionListener; import android.view.IWindowManager; import android.view.InputDevice; import android.view.InputEvent; -import android.view.InputMonitor; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; @@ -104,6 +104,7 @@ import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import javax.inject.Inject; @@ -151,7 +152,12 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() { @Override public void onTaskStackChanged() { - mGestureBlockingActivityRunning = isGestureBlockingActivityRunning(); + if (edgebackGestureHandlerGetRunningTasksBackground()) { + mBackgroundExecutor.execute(() -> mGestureBlockingActivityRunning.set( + isGestureBlockingActivityRunning())); + } else { + mGestureBlockingActivityRunning.set(isGestureBlockingActivityRunning()); + } } @Override public void onTaskCreated(int taskId, ComponentName componentName) { @@ -241,6 +247,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack private final PointF mDownPoint = new PointF(); private final PointF mEndPoint = new PointF(); + private AtomicBoolean mGestureBlockingActivityRunning = new AtomicBoolean(); + private boolean mThresholdCrossed = false; private boolean mAllowGesture = false; private boolean mLogGesture = false; @@ -256,7 +264,6 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack private boolean mIsEnabled; private boolean mIsNavBarShownTransiently; private boolean mIsBackGestureAllowed; - private boolean mGestureBlockingActivityRunning; private boolean mIsNewBackAffordanceEnabled; private boolean mIsTrackpadGestureFeaturesEnabled; private boolean mIsTrackpadThreeFingerSwipe; @@ -1017,7 +1024,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack mInRejectedExclusion = false; boolean isWithinInsets = isWithinInsets((int) ev.getX(), (int) ev.getY()); boolean isBackAllowedCommon = !mDisabledForQuickstep && mIsBackGestureAllowed - && !mGestureBlockingActivityRunning + && !mGestureBlockingActivityRunning.get() && !QuickStepContract.isBackGestureDisabled(mSysUiFlags, mIsTrackpadThreeFingerSwipe) && !isTrackpadScroll(mIsTrackpadGestureFeaturesEnabled, ev); @@ -1053,8 +1060,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack curTime, curTimeStr, mAllowGesture, mIsTrackpadThreeFingerSwipe, mIsOnLeftEdge, mDeferSetIsOnLeftEdge, mIsBackGestureAllowed, QuickStepContract.isBackGestureDisabled(mSysUiFlags, - mIsTrackpadThreeFingerSwipe), - mDisabledForQuickstep, mGestureBlockingActivityRunning, mIsInPip, mDisplaySize, + mIsTrackpadThreeFingerSwipe), mDisabledForQuickstep, + mGestureBlockingActivityRunning.get(), mIsInPip, mDisplaySize, mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion)); } else if (mAllowGesture || mLogGesture) { if (!mThresholdCrossed) { @@ -1236,7 +1243,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack pw.println(" mIsBackGestureAllowed=" + mIsBackGestureAllowed); pw.println(" mIsGestureHandlingEnabled=" + mIsGestureHandlingEnabled); pw.println(" mIsNavBarShownTransiently=" + mIsNavBarShownTransiently); - pw.println(" mGestureBlockingActivityRunning=" + mGestureBlockingActivityRunning); + pw.println(" mGestureBlockingActivityRunning=" + mGestureBlockingActivityRunning.get()); pw.println(" mAllowGesture=" + mAllowGesture); pw.println(" mUseMLModel=" + mUseMLModel); pw.println(" mDisabledForQuickstep=" + mDisabledForQuickstep); diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt index 55d7f8e0f740..8a84496e5e32 100644 --- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt +++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt @@ -19,10 +19,13 @@ package com.android.systemui.recordissue import android.app.NotificationManager import android.content.Context import android.content.Intent +import android.content.pm.LauncherApps import android.content.res.Resources import android.net.Uri import android.os.Handler import android.os.UserHandle +import android.util.Log +import androidx.core.content.FileProvider import com.android.internal.logging.UiEventLogger import com.android.systemui.dagger.qualifiers.LongRunning import com.android.systemui.dagger.qualifiers.Main @@ -34,7 +37,12 @@ import com.android.systemui.settings.UserContextProvider import com.android.systemui.statusbar.phone.KeyguardDismissUtil import com.android.traceur.FileSender import com.android.traceur.TraceUtils +import java.io.File +import java.io.FileOutputStream +import java.nio.file.Files import java.util.concurrent.Executor +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream import javax.inject.Inject class IssueRecordingService @@ -86,6 +94,10 @@ constructor( } ACTION_STOP, ACTION_STOP_NOTIF -> { + // ViewCapture needs to save it's data before it is disabled, or else the data will + // be lost. This is expected to change in the near future, and when that happens + // this line should be removed. + getSystemService(LauncherApps::class.java)?.saveViewCaptureData() TraceUtils.traceStop(contentResolver) } ACTION_SHARE -> { @@ -102,21 +114,22 @@ constructor( } private fun shareRecording(intent: Intent) { - val files = TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).get() - val traceUris: MutableList<Uri> = FileSender.getUriForFiles(this, files, AUTHORITY) - - if ( - intent.hasExtra(EXTRA_PATH) && intent.getStringExtra(EXTRA_PATH)?.isNotEmpty() == true - ) { - traceUris.add(Uri.parse(intent.getStringExtra(EXTRA_PATH))) - } - + val sharableUri: Uri = + zipAndPackageRecordings( + TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).get(), + intent.getStringExtra(EXTRA_PATH) + ) + ?: return val sendIntent = - FileSender.buildSendIntent(this, traceUris).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + FileSender.buildSendIntent(this, listOf(sharableUri)) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) if (mNotificationId != NOTIF_BASE_ID) { - val currentUserId = mUserContextTracker.userContext.userId - mNotificationManager.cancelAsUser(null, mNotificationId, UserHandle(currentUserId)) + mNotificationManager.cancelAsUser( + null, + mNotificationId, + UserHandle(mUserContextTracker.userContext.userId) + ) } // TODO: Debug why the notification shade isn't closing upon starting the BetterBug activity @@ -130,11 +143,39 @@ constructor( ) } + private fun zipAndPackageRecordings(traceFiles: List<File>, screenRecordingUri: String?): Uri? { + try { + externalCacheDir?.mkdirs() + val outZip: File = File.createTempFile(TEMP_FILE_PREFIX, ZIP_SUFFIX, externalCacheDir) + ZipOutputStream(FileOutputStream(outZip)).use { os -> + traceFiles.forEach { file -> + os.putNextEntry(ZipEntry(file.name)) + Files.copy(file.toPath(), os) + os.closeEntry() + } + if (screenRecordingUri != null) { + contentResolver.openInputStream(Uri.parse(screenRecordingUri))?.use { + os.putNextEntry(ZipEntry(SCREEN_RECORDING_ZIP_LABEL)) + it.transferTo(os) + os.closeEntry() + } + } + } + return FileProvider.getUriForFile(this, AUTHORITY, outZip) + } catch (e: Exception) { + Log.e(TAG, "Failed to zip and package Recordings. Cannot share with BetterBug.", e) + return null + } + } + companion object { private const val TAG = "IssueRecordingService" private const val CHANNEL_ID = "issue_record" private const val EXTRA_SCREEN_RECORD = "extra_screenRecord" private const val EXTRA_WINSCOPE_TRACING = "extra_winscopeTracing" + private const val ZIP_SUFFIX = ".zip" + private const val TEMP_FILE_PREFIX = "issue_recording" + private const val SCREEN_RECORDING_ZIP_LABEL = "screen-recording.mp4" private val DEFAULT_TRACE_TAGS = listOf<String>() private const val DEFAULT_BUFFER_SIZE = 16384 diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt index a490fe2db5bc..451fd679969a 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt @@ -25,6 +25,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shade.shared.model.ShadeMode import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted @@ -40,17 +41,17 @@ constructor( shadeInteractor: ShadeInteractor, ) { val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> = - shadeInteractor.isSplitShade - .map { isSplitShade -> destinationScenes(isSplitShade = isSplitShade) } + shadeInteractor.shadeMode + .map { shadeMode -> destinationScenes(shadeMode = shadeMode) } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = destinationScenes(isSplitShade = shadeInteractor.isSplitShade.value) + initialValue = destinationScenes(shadeMode = shadeInteractor.shadeMode.value) ) - private fun destinationScenes(isSplitShade: Boolean): Map<UserAction, UserActionResult> { + private fun destinationScenes(shadeMode: ShadeMode): Map<UserAction, UserActionResult> { return buildMap { - if (!isSplitShade) { + if (shadeMode == ShadeMode.Single) { this[ Swipe( pointerCount = 2, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt index d8c38503dbaf..f01e9bea1372 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt @@ -24,6 +24,7 @@ import android.graphics.Rect import android.graphics.drawable.Drawable import android.util.Log import android.view.Display +import android.view.KeyEvent import android.view.LayoutInflater import android.view.ScrollCaptureResponse import android.view.View @@ -34,12 +35,15 @@ import android.window.OnBackInvokedDispatcher import com.android.internal.logging.UiEventLogger import com.android.systemui.flags.FeatureFlags import com.android.systemui.res.R +import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS +import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER /** * Legacy implementation of screenshot view methods. Just proxies the calls down into the original * ScreenshotView. */ -class LegacyScreenshotViewProxy(context: Context) : ScreenshotViewProxy { +class LegacyScreenshotViewProxy(context: Context, private val logger: UiEventLogger) : + ScreenshotViewProxy { override val view: ScreenshotView = LayoutInflater.from(context).inflate(R.layout.screenshot, null) as ScreenshotView override val screenshotPreview: View @@ -52,13 +56,6 @@ class LegacyScreenshotViewProxy(context: Context) : ScreenshotViewProxy { set(value) { view.setDefaultTimeoutMillis(value) } - override var onBackInvokedCallback: OnBackInvokedCallback = OnBackInvokedCallback { - Log.wtf(TAG, "OnBackInvoked called before being set!") - } - override var onKeyListener: View.OnKeyListener? = null - set(value) { - view.setOnKeyListener(value) - } override var flags: FeatureFlags? = null set(value) { view.setFlags(value) @@ -67,10 +64,6 @@ class LegacyScreenshotViewProxy(context: Context) : ScreenshotViewProxy { set(value) { view.setPackageName(value) } - override var logger: UiEventLogger? = null - set(value) { - view.setUiEventLogger(value) - } override var callbacks: ScreenshotView.ScreenshotViewCallback? = null set(value) { view.setCallbacks(value) @@ -88,31 +81,9 @@ class LegacyScreenshotViewProxy(context: Context) : ScreenshotViewProxy { get() = view.isPendingSharedTransition init { - - view.addOnAttachStateChangeListener( - object : View.OnAttachStateChangeListener { - override fun onViewAttachedToWindow(view: View) { - if (LogConfig.DEBUG_INPUT) { - Log.d(TAG, "Registering Predictive Back callback") - } - view - .findOnBackInvokedDispatcher() - ?.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, - onBackInvokedCallback - ) - } - - override fun onViewDetachedFromWindow(view: View) { - if (LogConfig.DEBUG_INPUT) { - Log.d(TAG, "Unregistering Predictive Back callback") - } - view - .findOnBackInvokedDispatcher() - ?.unregisterOnBackInvokedCallback(onBackInvokedCallback) - } - } - ) + view.setUiEventLogger(logger) + addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) } + setOnKeyListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) } if (LogConfig.DEBUG_WINDOW) { Log.d(TAG, "adding OnComputeInternalInsetsListener") } @@ -135,7 +106,20 @@ class LegacyScreenshotViewProxy(context: Context) : ScreenshotViewProxy { override fun setChipIntents(imageData: ScreenshotController.SavedImageData) = view.setChipIntents(imageData) - override fun animateDismissal() = view.animateDismissal() + override fun requestDismissal(event: ScreenshotEvent) { + if (DEBUG_DISMISS) { + Log.d(TAG, "screenshot dismissal requested") + } + // If we're already animating out, don't restart the animation + if (view.isDismissing) { + if (DEBUG_DISMISS) { + Log.v(TAG, "Already dismissing, ignoring duplicate command $event") + } + return + } + logger.log(event, 0, packageName) + view.animateDismissal() + } override fun showScrollChip(packageName: String, onClick: Runnable) = view.showScrollChip(packageName, onClick) @@ -177,9 +161,58 @@ class LegacyScreenshotViewProxy(context: Context) : ScreenshotViewProxy { view.post(runnable) } + private fun addPredictiveBackListener(onDismissRequested: (ScreenshotEvent) -> Unit) { + val onBackInvokedCallback = OnBackInvokedCallback { + if (LogConfig.DEBUG_INPUT) { + Log.d(TAG, "Predictive Back callback dispatched") + } + onDismissRequested.invoke(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER) + } + view.addOnAttachStateChangeListener( + object : View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(v: View) { + if (LogConfig.DEBUG_INPUT) { + Log.d(TAG, "Registering Predictive Back callback") + } + view + .findOnBackInvokedDispatcher() + ?.registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, + onBackInvokedCallback + ) + } + + override fun onViewDetachedFromWindow(view: View) { + if (LogConfig.DEBUG_INPUT) { + Log.d(TAG, "Unregistering Predictive Back callback") + } + view + .findOnBackInvokedDispatcher() + ?.unregisterOnBackInvokedCallback(onBackInvokedCallback) + } + } + ) + } + private fun setOnKeyListener(onDismissRequested: (ScreenshotEvent) -> Unit) { + view.setOnKeyListener( + object : View.OnKeyListener { + override fun onKey(view: View, keyCode: Int, event: KeyEvent): Boolean { + if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) { + if (LogConfig.DEBUG_INPUT) { + Log.d(TAG, "onKeyEvent: $keyCode") + } + onDismissRequested.invoke(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER) + return true + } + return false + } + } + ) + } + class Factory : ScreenshotViewProxy.Factory { - override fun getProxy(context: Context): ScreenshotViewProxy { - return LegacyScreenshotViewProxy(context) + override fun getProxy(context: Context, logger: UiEventLogger): ScreenshotViewProxy { + return LegacyScreenshotViewProxy(context, logger) } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 1ca9b985b090..6bab956ca09a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -21,7 +21,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT; import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM; import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK; -import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS; import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT; import static com.android.systemui.screenshot.LogConfig.DEBUG_UI; import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW; @@ -64,7 +63,6 @@ import android.util.Pair; import android.view.Display; import android.view.IRemoteAnimationFinishedCallback; import android.view.IRemoteAnimationRunner; -import android.view.KeyEvent; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; import android.view.ScrollCaptureResponse; @@ -333,12 +331,7 @@ public class ScreenshotController { mScreenshotHandler = timeoutHandler; mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS); - mScreenshotHandler.setOnTimeoutRunnable(() -> { - if (DEBUG_UI) { - Log.d(TAG, "Corner timeout hit"); - } - dismissScreenshot(SCREENSHOT_INTERACTION_TIMEOUT); - }); + mDisplayId = displayId; mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class)); @@ -351,7 +344,14 @@ public class ScreenshotController { mMessageContainerController = messageContainerController; mAssistContentRequester = assistContentRequester; - mViewProxy = viewProxyFactory.getProxy(mContext); + mViewProxy = viewProxyFactory.getProxy(mContext, mUiEventLogger); + + mScreenshotHandler.setOnTimeoutRunnable(() -> { + if (DEBUG_UI) { + Log.d(TAG, "Corner timeout hit"); + } + mViewProxy.requestDismissal(SCREENSHOT_INTERACTION_TIMEOUT); + }); mAccessibilityManager = AccessibilityManager.getInstance(mContext); @@ -376,7 +376,7 @@ public class ScreenshotController { @Override public void onReceive(Context context, Intent intent) { if (ClipboardOverlayController.COPY_OVERLAY_ACTION.equals(intent.getAction())) { - dismissScreenshot(SCREENSHOT_DISMISSED_OTHER); + mViewProxy.requestDismissal(SCREENSHOT_DISMISSED_OTHER); } } }; @@ -525,22 +525,11 @@ public class ScreenshotController { } /** - * Clears current screenshot + * Requests the view to dismiss the current screenshot (may be ignored, if screenshot is already + * being dismissed) */ - void dismissScreenshot(ScreenshotEvent event) { - if (DEBUG_DISMISS) { - Log.d(TAG, "dismissScreenshot"); - } - // If we're already animating out, don't restart the animation - if (mViewProxy.isDismissing()) { - if (DEBUG_DISMISS) { - Log.v(TAG, "Already dismissing, ignoring duplicate command"); - } - return; - } - mUiEventLogger.log(event, 0, mPackageName); - mScreenshotHandler.cancelTimeout(); - mViewProxy.animateDismissal(); + void requestDismissal(ScreenshotEvent event) { + mViewProxy.requestDismissal(event); } boolean isPendingSharedTransition() { @@ -572,10 +561,6 @@ public class ScreenshotController { mScreenshotSoundController.releaseScreenshotSoundAsync(); } - private void respondToKeyDismissal() { - dismissScreenshot(SCREENSHOT_DISMISSED_OTHER); - } - /** * Update resources on configuration change. Reinflate for theme/color changes. */ @@ -585,13 +570,6 @@ public class ScreenshotController { } mMessageContainerController.setView(mViewProxy.getView()); - mViewProxy.setLogger(mUiEventLogger); - mViewProxy.setOnBackInvokedCallback(() -> { - if (DEBUG_INPUT) { - Log.d(TAG, "Predictive Back callback dispatched"); - } - respondToKeyDismissal(); - }); mViewProxy.setCallbacks(new ScreenshotView.ScreenshotViewCallback() { @Override public void onUserInteraction() { @@ -622,17 +600,6 @@ public class ScreenshotController { mViewProxy.setDefaultDisplay(mDisplayId); mViewProxy.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis()); - mViewProxy.setOnKeyListener((v, keyCode, event) -> { - if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) { - if (DEBUG_INPUT) { - Log.d(TAG, "onKeyEvent: " + keyCode); - } - respondToKeyDismissal(); - return true; - } - return false; - }); - if (DEBUG_WINDOW) { Log.d(TAG, "setContentView: " + mViewProxy.getView()); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt index 381404a85587..d5c7f95ce289 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt @@ -24,11 +24,9 @@ import android.graphics.Rect import android.graphics.drawable.Drawable import android.view.ScrollCaptureResponse import android.view.View -import android.view.View.OnKeyListener import android.view.ViewGroup import android.view.ViewTreeObserver import android.view.WindowInsets -import android.window.OnBackInvokedCallback import com.android.internal.logging.UiEventLogger import com.android.systemui.flags.FeatureFlags @@ -39,11 +37,8 @@ interface ScreenshotViewProxy { var defaultDisplay: Int var defaultTimeoutMillis: Long - var onBackInvokedCallback: OnBackInvokedCallback - var onKeyListener: OnKeyListener? var flags: FeatureFlags? var packageName: String - var logger: UiEventLogger? var callbacks: ScreenshotView.ScreenshotViewCallback? var screenshot: ScreenshotData? @@ -58,7 +53,7 @@ interface ScreenshotViewProxy { fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator fun addQuickShareChip(quickShareAction: Notification.Action) fun setChipIntents(imageData: ScreenshotController.SavedImageData) - fun animateDismissal() + fun requestDismissal(event: ScreenshotEvent) fun showScrollChip(packageName: String, onClick: Runnable) fun hideScrollChip() @@ -82,6 +77,6 @@ interface ScreenshotViewProxy { fun post(runnable: Runnable) interface Factory { - fun getProxy(context: Context): ScreenshotViewProxy + fun getProxy(context: Context, logger: UiEventLogger): ScreenshotViewProxy } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt index e464fd0c8e8e..bc3375502dd4 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt @@ -136,7 +136,7 @@ constructor( fun onCloseSystemDialogsReceived() { screenshotControllers.forEach { (_, screenshotController) -> if (!screenshotController.isPendingSharedTransition) { - screenshotController.dismissScreenshot(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER) + screenshotController.requestDismissal(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER) } } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 0991c9a326c8..9cf347bc8569 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -53,9 +53,9 @@ import android.widget.Toast; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.internal.util.ScreenshotRequest; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.res.R; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -89,7 +89,7 @@ public class TakeScreenshotService extends Service { // TODO(b/295143676): move receiver inside executor when the flag is enabled. mTakeScreenshotExecutor.get().onCloseSystemDialogsReceived(); } else if (!mScreenshot.isPendingSharedTransition()) { - mScreenshot.dismissScreenshot(SCREENSHOT_DISMISSED_OTHER); + mScreenshot.requestDismissal(SCREENSHOT_DISMISSED_OTHER); } } } diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java index e92630fc67a2..92d6ec97ad83 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java @@ -63,7 +63,7 @@ import java.util.concurrent.Executor; public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController { private static final String TAG = "CentralSurfaces.BrightnessController"; - private static final int SLIDER_ANIMATION_DURATION = 1000; + private static final int SLIDER_ANIMATION_DURATION = 3000; private static final int MSG_UPDATE_SLIDER = 1; private static final int MSG_ATTACH_LISTENER = 2; @@ -96,6 +96,7 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig }; private volatile boolean mAutomatic; // Brightness adjusted automatically using ambient light. + private boolean mTrackingTouch = false; // Brightness adjusted via touch events. private volatile boolean mIsVrModeEnabled; private boolean mListening; private boolean mExternalChange; @@ -330,6 +331,7 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig @Override public void onChanged(boolean tracking, int value, boolean stopTracking) { + mTrackingTouch = tracking; if (mExternalChange) return; if (mSliderAnimator != null) { @@ -396,6 +398,12 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig } } + private boolean triggeredByBrightnessKey() { + // When the brightness mode is manual and the user isn't changing the brightness via the + // brightness slider, assume changes are coming from a brightness key. + return !mAutomatic && !mTrackingTouch; + } + private void updateSlider(float brightnessValue, boolean inVrMode) { final float min = mBrightnessMin; final float max = mBrightnessMax; @@ -417,12 +425,13 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig } private void animateSliderTo(int target) { - if (!mControlValueInitialized || !mControl.isVisible()) { + if (!mControlValueInitialized || !mControl.isVisible() || triggeredByBrightnessKey()) { // Don't animate the first value since its default state isn't meaningful to users. // We also don't want to animate slider if it's not visible - especially important when // two sliders are active at the same time in split shade (one in QS and one in QQS), // as this negatively affects transition between them and they share mirror slider - - // animating it from two different sources causes janky motion + // animating it from two different sources causes janky motion. + // Don't animate if the value is changed via the brightness keys of a keyboard. mControl.setValue(target); mControlValueInitialized = true; } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index cdb520fb0879..b8675500a351 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -1540,13 +1540,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardBottomArea = keyguardBottomArea; } - @Override + /** Sets a listener to be notified when the shade starts opening or finishes closing. */ public void setOpenCloseListener(OpenCloseListener openCloseListener) { SceneContainerFlag.assertInLegacyMode(); mOpenCloseListener = openCloseListener; } - @Override + /** Sets a listener to be notified when touch tracking begins. */ public void setTrackingStartedListener(TrackingStartedListener trackingStartedListener) { mTrackingStartedListener = trackingStartedListener; } @@ -2603,7 +2603,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return maxHeight; } - @Override public boolean isExpandingOrCollapsing() { float lockscreenExpansionProgress = mQsController.getLockscreenShadeDragProgress(); return mIsExpandingOrCollapsing diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt b/packages/SystemUI/src/com/android/systemui/shade/OpenCloseListener.kt index 98a9e93047ae..108dd47874c3 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/OpenCloseListener.kt @@ -14,16 +14,13 @@ * limitations under the License. */ -package com.android.credentialmanager.ui.screens +package com.android.systemui.shade -import androidx.activity.result.IntentSenderRequest +/** Listens for when shade begins opening or finishes closing. */ +interface OpenCloseListener { + /** Called when the shade finishes closing. */ + fun onClosingFinished() -sealed class UiState { - data object CredentialScreen : UiState() - - data class CredentialSelected( - val intentSenderRequest: IntentSenderRequest? - ) : UiState() - - data object Cancel : UiState() + /** Called when the shade starts opening. */ + fun onOpenStarted() } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt index 27168a799086..177c3db6b720 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt @@ -89,8 +89,7 @@ constructor( override fun isShadeFullyOpen(): Boolean = shadeInteractor.isAnyFullyExpanded.value - override fun isExpandingOrCollapsing(): Boolean = - shadeInteractor.anyExpansion.value > 0f && shadeInteractor.anyExpansion.value < 1f + override fun isExpandingOrCollapsing(): Boolean = shadeInteractor.isUserInteracting.value override fun instantExpandShade() { // Do nothing diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLockscreenInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLockscreenInteractor.kt index a9ba6f96b7d2..859fce53a371 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLockscreenInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLockscreenInteractor.kt @@ -27,9 +27,6 @@ interface ShadeLockscreenInteractor { */ @Deprecated("Use ShadeInteractor instead") fun expandToNotifications() - /** Returns whether the shade is expanding or collapsing itself or quick settings. */ - val isExpandingOrCollapsing: Boolean - /** * Returns whether the shade height is greater than zero (i.e. partially or fully expanded), * there is a HUN, the shade is animating, or the shade is instantly expanding. diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt index 07236d1e5ab7..de21a73e312b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt @@ -31,9 +31,6 @@ import java.util.function.Consumer * @see NotificationPanelViewController */ interface ShadeViewController { - /** Returns whether the shade is expanding or collapsing itself or quick settings. */ - val isExpandingOrCollapsing: Boolean - /** * Returns whether the shade height is greater than zero or the shade is expecting a synthesized * down event. @@ -52,15 +49,9 @@ interface ShadeViewController { /** Returns whether the shade's top level view is enabled. */ val isViewEnabled: Boolean - /** Sets a listener to be notified when the shade starts opening or finishes closing. */ - fun setOpenCloseListener(openCloseListener: OpenCloseListener) - /** Returns whether status bar icons should be hidden when the shade is expanded. */ fun shouldHideStatusBarIconsWhenExpanded(): Boolean - /** Sets a listener to be notified when touch tracking begins. */ - fun setTrackingStartedListener(trackingStartedListener: TrackingStartedListener) - /** * Disables the shade header. * @@ -250,17 +241,3 @@ interface ShadeViewStateProvider { /** Return the fraction of the shade that's expanded, when in lockscreen. */ val lockscreenShadeDragProgress: Float } - -/** Listens for when touch tracking begins. */ -interface TrackingStartedListener { - fun onTrackingStarted() -} - -/** Listens for when shade begins opening or finishes closing. */ -interface OpenCloseListener { - /** Called when the shade finishes closing. */ - fun onClosingFinished() - - /** Called when the shade starts opening. */ - fun onOpenStarted() -} diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt index 6e036863216b..b67156f4b982 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt @@ -34,7 +34,6 @@ class ShadeViewControllerEmptyImpl @Inject constructor() : ShadeLockscreenInteractor, PanelExpansionInteractor { override fun expandToNotifications() {} - override val isExpandingOrCollapsing: Boolean = false override val isExpanded: Boolean = false override val isPanelExpanded: Boolean = false override fun animateCollapseQs(fullyCollapse: Boolean) {} @@ -43,10 +42,8 @@ class ShadeViewControllerEmptyImpl @Inject constructor() : override val isFullyCollapsed: Boolean = false override val isTracking: Boolean = false override val isViewEnabled: Boolean = false - override fun setOpenCloseListener(openCloseListener: OpenCloseListener) {} override fun shouldHideStatusBarIconsWhenExpanded() = false override fun blockExpansionForCurrentTouch() {} - override fun setTrackingStartedListener(trackingStartedListener: TrackingStartedListener) {} override fun disableHeader(state1: Int, state2: Int, animated: Boolean) {} override fun startExpandLatencyTracking() {} override fun startBouncerPreHideAnimation() {} diff --git a/packages/SystemUI/src/com/android/systemui/shade/TrackingStartedListener.kt b/packages/SystemUI/src/com/android/systemui/shade/TrackingStartedListener.kt new file mode 100644 index 000000000000..3803c27d5f37 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/TrackingStartedListener.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade + +/** Listens for when touch tracking begins. */ +interface TrackingStartedListener { + fun onTrackingStarted() +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt index e050c0b7d06d..5c79e1ee84f3 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt @@ -16,6 +16,7 @@ package com.android.systemui.shade.data.repository import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.shade.shared.model.ShadeMode import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -100,8 +101,7 @@ interface ShadeRepository { @Deprecated("Use ShadeInteractor.isQsBypassingShade instead") val legacyExpandImmediate: StateFlow<Boolean> - /** Whether the current configuration requires the split shade to be shown. */ - val isSplitShade: StateFlow<Boolean> + val shadeMode: StateFlow<ShadeMode> /** True when QS is taking up the entire screen, i.e. fully expanded on a non-unfolded phone. */ @Deprecated("Use ShadeInteractor instead") val legacyQsFullscreen: StateFlow<Boolean> @@ -109,7 +109,7 @@ interface ShadeRepository { /** NPVC.mClosing as a flow. */ @Deprecated("Use ShadeAnimationInteractor instead") val legacyIsClosing: StateFlow<Boolean> - fun setSplitShade(isSplitShade: Boolean) + fun setShadeMode(mode: ShadeMode) /** Sets whether a closing animation is happening. */ @Deprecated("Use ShadeAnimationInteractor instead") fun setLegacyIsClosing(isClosing: Boolean) @@ -219,11 +219,11 @@ class ShadeRepositoryImpl @Inject constructor() : ShadeRepository { @Deprecated("Use ShadeInteractor instead") override val legacyQsFullscreen: StateFlow<Boolean> = _legacyQsFullscreen.asStateFlow() - val _isSplitShade = MutableStateFlow(false) - override val isSplitShade: StateFlow<Boolean> = _isSplitShade.asStateFlow() + val _shadeMode = MutableStateFlow<ShadeMode>(ShadeMode.Single) + override val shadeMode: StateFlow<ShadeMode> = _shadeMode.asStateFlow() - override fun setSplitShade(isSplitShade: Boolean) { - _isSplitShade.value = isSplitShade + override fun setShadeMode(shadeMode: ShadeMode) { + _shadeMode.value = shadeMode } override fun setLegacyQsFullscreen(legacyQsFullscreen: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt index ad3fbe574948..bc60c838b703 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.shade.domain.interactor +import com.android.systemui.shade.shared.model.ShadeMode import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted @@ -103,8 +104,7 @@ interface BaseShadeInteractor { */ val isUserInteractingWithQs: Flow<Boolean> - /** Whether the current configuration requires the split shade to be shown. */ - val isSplitShade: StateFlow<Boolean> + val shadeMode: StateFlow<ShadeMode> } fun createAnyExpansionFlow( diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt index 57a36b50922d..e9bb4c623013 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt @@ -17,6 +17,7 @@ package com.android.systemui.shade.domain.interactor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.shade.shared.model.ShadeMode import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -42,5 +43,5 @@ class ShadeInteractorEmptyImpl @Inject constructor() : ShadeInteractor { override val isUserInteracting: StateFlow<Boolean> = inactiveFlowBoolean override val isShadeTouchable: Flow<Boolean> = inactiveFlowBoolean override val isExpandToQsEnabled: Flow<Boolean> = inactiveFlowBoolean - override val isSplitShade: StateFlow<Boolean> = inactiveFlowBoolean + override val shadeMode: StateFlow<ShadeMode> = MutableStateFlow(ShadeMode.Single) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt index 3bccd2be4933..6414af36b4dd 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt @@ -21,6 +21,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -99,7 +100,7 @@ constructor( override val isUserInteractingWithQs: Flow<Boolean> = userInteractingFlow(repository.legacyQsTracking, repository.qsExpansion) - override val isSplitShade: StateFlow<Boolean> = repository.isSplitShade + override val shadeMode: StateFlow<ShadeMode> = repository.shadeMode /** * Return a flow for whether a user is interacting with an expandable shade component using diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt index ad8c02912168..7785eda4bd6a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt @@ -23,6 +23,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -108,7 +109,7 @@ constructor( override val isUserInteractingWithQs: Flow<Boolean> = sceneBasedInteracting(sceneInteractor, Scenes.QuickSettings) - override val isSplitShade: StateFlow<Boolean> = shadeRepository.isSplitShade + override val shadeMode: StateFlow<ShadeMode> = shadeRepository.shadeMode /** * Returns a flow that uses scene transition progress to and from a scene that is pulled down diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt index 1f78ae8b6e99..3d9337ee5c89 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt @@ -38,8 +38,6 @@ constructor( changeToShadeScene() } - override val isExpandingOrCollapsing = shadeInteractor.isUserInteracting.value - override val isExpanded = shadeInteractor.isAnyExpanded.value override fun startBouncerPreHideAnimation() { diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt index 334908eedcb4..11ce818bbdeb 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt @@ -22,6 +22,7 @@ import com.android.systemui.common.ui.data.repository.ConfigurationRepository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.statusbar.policy.SplitShadeStateController import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -41,17 +42,25 @@ constructor( ) : CoreStartable { override fun start() { - hydrateSplitShade() + hydrateShadeMode() } - private fun hydrateSplitShade() { + private fun hydrateShadeMode() { applicationScope.launch { configurationRepository.onAnyConfigurationChange // Force initial collection. .onStart { emit(Unit) } .map { applicationContext.resources } .map { resources -> controller.shouldUseSplitNotificationShade(resources) } - .collect { isSplitShade -> shadeRepository.setSplitShade(isSplitShade) } + .collect { isSplitShade -> + shadeRepository.setShadeMode( + if (isSplitShade) { + ShadeMode.Split + } else { + ShadeMode.Single + } + ) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt new file mode 100644 index 000000000000..3451eaf54063 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade.shared.model + +/** Enumerates all known modes of operation of the shade. */ +sealed interface ShadeMode { + + /** + * The single or "accordion" shade where the QS and notification parts are in two vertically + * stacked panels and the user can swipe up and down to expand or collapse between the two + * parts. + */ + data object Single : ShadeMode + + /** + * The split shade where, on large screens and unfolded foldables, the QS and notification parts + * are placed side-by-side and expand/collapse as a single panel. + */ + data object Split : ShadeMode + + /** + * The dual shade where the QS and notification parts each have their own independently + * expandable/collapsible panel on either side of the large screen / unfolded device or sharing + * a space on a small screen or folded device. + */ + data object Dual : ShadeMode +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt index 8084a6f390a7..ea549f2b7e53 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt @@ -33,6 +33,7 @@ import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject @@ -64,12 +65,12 @@ constructor( combine( deviceEntryInteractor.isUnlocked, deviceEntryInteractor.canSwipeToEnter, - shadeInteractor.isSplitShade, - ) { isUnlocked, canSwipeToDismiss, isSplitShade -> + shadeInteractor.shadeMode, + ) { isUnlocked, canSwipeToDismiss, shadeMode -> destinationScenes( isUnlocked = isUnlocked, canSwipeToDismiss = canSwipeToDismiss, - isSplitShade = isSplitShade, + shadeMode = shadeMode, ) } .stateIn( @@ -79,7 +80,7 @@ constructor( destinationScenes( isUnlocked = deviceEntryInteractor.isUnlocked.value, canSwipeToDismiss = deviceEntryInteractor.canSwipeToEnter.value, - isSplitShade = shadeInteractor.isSplitShade.value, + shadeMode = shadeInteractor.shadeMode.value, ), ) @@ -96,8 +97,7 @@ constructor( initialValue = false ) - /** Whether the current configuration requires the split shade to be shown. */ - val isSplitShade: StateFlow<Boolean> = shadeInteractor.isSplitShade + val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode /** Notifies that some content in the shade was clicked. */ fun onContentClicked() = deviceEntryInteractor.attemptDeviceEntry() @@ -119,7 +119,7 @@ constructor( private fun destinationScenes( isUnlocked: Boolean, canSwipeToDismiss: Boolean?, - isSplitShade: Boolean, + shadeMode: ShadeMode, ): Map<UserAction, UserActionResult> { val up = when { @@ -128,7 +128,7 @@ constructor( else -> Scenes.Lockscreen } - val down = if (isSplitShade) null else Scenes.QuickSettings + val down = Scenes.QuickSettings.takeIf { shadeMode is ShadeMode.Single } return buildMap { this[Swipe(SwipeDirection.Up)] = UserActionResult(up) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 615534809c97..5171a5c9144c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -39,8 +39,6 @@ import com.android.app.animation.Interpolators; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.SystemBarUtils; import com.android.systemui.animation.ShadeInterpolation; -import com.android.systemui.flags.Flags; -import com.android.systemui.flags.RefactorFlag; import com.android.systemui.res.R; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.notification.ColorUpdateLogger; @@ -95,8 +93,6 @@ public class NotificationShelf extends ActivatableNotificationView { private float mCornerAnimationDistance; private float mActualWidth = -1; private int mMaxIconsOnLockscreen; - private final RefactorFlag mSensitiveRevealAnim = - RefactorFlag.forView(Flags.SENSITIVE_REVEAL_ANIM); private boolean mCanModifyColorOfNotifications; private boolean mCanInteract; private NotificationStackScrollLayout mHostLayout; @@ -266,7 +262,7 @@ public class NotificationShelf extends ActivatableNotificationView { } final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight(); - if (mSensitiveRevealAnim.isEnabled() && viewState.hidden) { + if (viewState.hidden) { // if the shelf is hidden, position it at the end of the stack (plus the clip // padding), such that when it appears animated, it will smoothly move in from the // bottom, without jump cutting any notifications @@ -398,10 +394,6 @@ public class NotificationShelf extends ActivatableNotificationView { // find the first view that doesn't overlap with the shelf int notGoneIndex = 0; int colorOfViewBeforeLast = NO_COLOR; - boolean backgroundForceHidden = false; - if (mHideBackground && !((ShelfState) getViewState()).hasItemsInStableShelf) { - backgroundForceHidden = true; - } int colorTwoBefore = NO_COLOR; int previousColor = NO_COLOR; float transitionAmount = 0.0f; @@ -429,8 +421,7 @@ public class NotificationShelf extends ActivatableNotificationView { expandingAnimated, isLastChild, shelfClipStart); // TODO(b/172289889) scale mPaddingBetweenElements with expansion amount - if ((!mSensitiveRevealAnim.isEnabled() && ((isLastChild && !child.isInShelf()) - || backgroundForceHidden)) || aboveShelf) { + if (aboveShelf) { notificationClipEnd = shelfStart + getIntrinsicHeight(); } else { notificationClipEnd = shelfStart - mPaddingBetweenElements; @@ -440,8 +431,7 @@ public class NotificationShelf extends ActivatableNotificationView { // If the current row is an ExpandableNotificationRow, update its color, roundedness, // and icon state. - if (child instanceof ExpandableNotificationRow) { - ExpandableNotificationRow expandableRow = (ExpandableNotificationRow) child; + if (child instanceof ExpandableNotificationRow expandableRow) { numViewsInShelf += inShelfAmount; int ownColorUntinted = expandableRow.getBackgroundColorWithoutTint(); if (viewStart >= shelfStart && mNotGoneIndex == -1) { @@ -471,16 +461,8 @@ public class NotificationShelf extends ActivatableNotificationView { notGoneIndex++; } - if (child instanceof ActivatableNotificationView) { - ActivatableNotificationView anv = (ActivatableNotificationView) child; - // Because we show whole notifications on the lockscreen, the bottom notification is - // always "just about to enter the shelf" by normal scrolling rules. This is fine - // if the shelf is visible, but if the shelf is hidden, it causes incorrect curling. - // notificationClipEnd handles the discrepancy between a visible and hidden shelf, - // so we use that when on the keyguard (and while animating away) to reduce curling. - final float keyguardSafeShelfStart = !mSensitiveRevealAnim.isEnabled() - && mAmbientState.isOnKeyguard() ? notificationClipEnd : shelfStart; - updateCornerRoundnessOnScroll(anv, viewStart, keyguardSafeShelfStart); + if (child instanceof ActivatableNotificationView anv) { + updateCornerRoundnessOnScroll(anv, viewStart, shelfStart); } } @@ -519,11 +501,10 @@ public class NotificationShelf extends ActivatableNotificationView { mShelfIcons.applyIconStates(); for (int i = 0; i < getHostLayoutChildCount(); i++) { View child = getHostLayoutChildAt(i); - if (!(child instanceof ExpandableNotificationRow) + if (!(child instanceof ExpandableNotificationRow row) || child.getVisibility() == GONE) { continue; } - ExpandableNotificationRow row = (ExpandableNotificationRow) child; updateContinuousClipping(row); } boolean hideBackground = isHidden; @@ -613,8 +594,7 @@ public class NotificationShelf extends ActivatableNotificationView { private void clipTransientViews() { for (int i = 0; i < getHostLayoutTransientViewCount(); i++) { View transientView = getHostLayoutTransientView(i); - if (transientView instanceof ExpandableView) { - ExpandableView transientExpandableView = (ExpandableView) transientView; + if (transientView instanceof ExpandableView transientExpandableView) { updateNotificationClipHeight(transientExpandableView, getTranslationY(), -1); } } @@ -871,10 +851,9 @@ public class NotificationShelf extends ActivatableNotificationView { } private void setIconTransformationAmount(ExpandableView view, float transitionAmount) { - if (!(view instanceof ExpandableNotificationRow)) { + if (!(view instanceof ExpandableNotificationRow row)) { return; } - ExpandableNotificationRow row = (ExpandableNotificationRow) view; StatusBarIconView icon = row.getShelfIcon(); NotificationIconContainer.IconState iconState = getIconState(icon); if (iconState == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 5f3a83aa35e0..589537ef713b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -278,8 +278,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private OnExpandClickListener mOnExpandClickListener; private View.OnClickListener mOnFeedbackClickListener; private Path mExpandingClipPath; - private final RefactorFlag mInlineReplyAnimation = - RefactorFlag.forView(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION); private static boolean shouldSimulateSlowMeasure() { return Compile.IS_DEBUG && RefactorFlag.forView( @@ -2880,8 +2878,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mSensitiveHiddenInGeneral = hideSensitive; int intrinsicAfter = getIntrinsicHeight(); if (intrinsicBefore != intrinsicAfter) { - boolean needsAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM); - notifyHeightChanged(needsAnimation); + notifyHeightChanged(true); } } @@ -3241,13 +3238,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mGuts.setActualHeight(height); return; } - int contentHeight = Math.max(getMinHeight(), height); for (NotificationContentView l : mLayouts) { - if (mInlineReplyAnimation.isEnabled()) { - l.setContentHeight(height); - } else { - l.setContentHeight(contentHeight); - } + l.setContentHeight(height); } if (mIsSummaryWithChildren) { mChildrenContainer.setActualHeight(height); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 137e1b2ab809..8a3e7e8a0580 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -699,8 +699,7 @@ public class NotificationContentView extends FrameLayout implements Notification int hint; if (mHeadsUpChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_HEADSUP)) { hint = getViewHeight(VISIBLE_TYPE_HEADSUP); - if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isAnimatingAppearance() - && mHeadsUpRemoteInputController.isFocusAnimationFlagActive()) { + if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isAnimatingAppearance()) { // While the RemoteInputView is animating its appearance, it should be allowed // to overlap the hint, therefore no space is reserved for the hint during the // appearance animation of the RemoteInputView diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index b20507117558..77e94257c832 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -87,7 +87,6 @@ import com.android.systemui.Dumpable; import com.android.systemui.ExpandHelper; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; -import com.android.systemui.flags.RefactorFlag; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.res.R; @@ -197,8 +196,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable */ private Set<Integer> mDebugTextUsedYPositions; private final boolean mDebugRemoveAnimation; - private final boolean mSensitiveRevealAnimEndabled; - private final RefactorFlag mAnimatedInsets; private int mContentHeight; private float mIntrinsicContentHeight; private int mPaddingBetweenElements; @@ -619,9 +616,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable Flags.LOCKSCREEN_ENABLE_LANDSCAPE); mDebugLines = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_LINES); mDebugRemoveAnimation = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION); - mSensitiveRevealAnimEndabled = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM); - mAnimatedInsets = - new RefactorFlag(mFeatureFlags, Flags.ANIMATED_NOTIFICATION_SHADE_INSETS); mSectionsManager = Dependency.get(NotificationSectionsManager.class); mScreenOffAnimationController = Dependency.get(ScreenOffAnimationController.class); @@ -656,9 +650,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mGroupMembershipManager = Dependency.get(GroupMembershipManager.class); mGroupExpansionManager = Dependency.get(GroupExpansionManager.class); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); - if (mAnimatedInsets.isEnabled()) { - setWindowInsetsAnimationCallback(mInsetsCallback); - } + setWindowInsetsAnimationCallback(mInsetsCallback); } /** @@ -1734,11 +1726,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return; } mForcedScroll = v; - if (mAnimatedInsets.isEnabled()) { - updateForcedScroll(); - } else { - scrollTo(v); - } + updateForcedScroll(); } public boolean scrollTo(View v) { @@ -1783,31 +1771,15 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { - if (!mAnimatedInsets.isEnabled()) { - mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom; - } mWaterfallTopInset = 0; final DisplayCutout cutout = insets.getDisplayCutout(); if (cutout != null) { mWaterfallTopInset = cutout.getWaterfallInsets().top; } - if (mAnimatedInsets.isEnabled() && !mIsInsetAnimationRunning) { + if (!mIsInsetAnimationRunning) { // update bottom inset e.g. after rotation updateBottomInset(insets); } - if (!mAnimatedInsets.isEnabled()) { - int range = getScrollRange(); - if (mOwnScrollY > range) { - // HACK: We're repeatedly getting staggered insets here while the IME is - // animating away. To work around that we'll wait until things have settled. - removeCallbacks(mReclamp); - postDelayed(mReclamp, 50); - } else if (mForcedScroll != null) { - // The scroll was requested before we got the actual inset - in case we need - // to scroll up some more do so now. - scrollTo(mForcedScroll); - } - } return insets; } @@ -2576,7 +2548,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return; } child.setOnHeightChangedListener(null); - if (child instanceof ExpandableNotificationRow && mSensitiveRevealAnimEndabled) { + if (child instanceof ExpandableNotificationRow) { NotificationEntry entry = ((ExpandableNotificationRow) child).getEntry(); entry.removeOnSensitivityChangedListener(mOnChildSensitivityChangedListener); } @@ -2872,7 +2844,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private void onViewAddedInternal(ExpandableView child) { updateHideSensitiveForChild(child); child.setOnHeightChangedListener(mOnChildHeightChangedListener); - if (child instanceof ExpandableNotificationRow && mSensitiveRevealAnimEndabled) { + if (child instanceof ExpandableNotificationRow) { NotificationEntry entry = ((ExpandableNotificationRow) child).getEntry(); entry.addOnSensitivityChangedListener(mOnChildSensitivityChangedListener); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index 84b39c1a4a2e..2745817d6d40 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -20,7 +20,6 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import com.android.systemui.common.shared.model.NotificationContainerBounds -import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dump.DumpManager @@ -37,8 +36,6 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED -import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING -import com.android.systemui.keyguard.shared.model.TransitionState.STARTED import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel @@ -97,7 +94,6 @@ constructor( private val keyguardInteractor: KeyguardInteractor, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val shadeInteractor: ShadeInteractor, - communalInteractor: CommunalInteractor, private val alternateBouncerToGoneTransitionViewModel: AlternateBouncerToGoneTransitionViewModel, private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, @@ -127,22 +123,6 @@ constructor( private val statesForConstrainedNotifications: Set<KeyguardState> = setOf(AOD, LOCKSCREEN, DOZING, ALTERNATE_BOUNCER, PRIMARY_BOUNCER) - private val lockscreenToGlanceableHubRunning = - keyguardTransitionInteractor - .transition(LOCKSCREEN, GLANCEABLE_HUB) - .map { it.transitionState == STARTED || it.transitionState == RUNNING } - .distinctUntilChanged() - .onStart { emit(false) } - .dumpWhileCollecting("lockscreenToGlanceableHubRunning") - - private val glanceableHubToLockscreenRunning = - keyguardTransitionInteractor - .transition(GLANCEABLE_HUB, LOCKSCREEN) - .map { it.transitionState == STARTED || it.transitionState == RUNNING } - .distinctUntilChanged() - .onStart { emit(false) } - .dumpWhileCollecting("glanceableHubToLockscreenRunning") - /** * Shade locked is a legacy concept, but necessary to mimic current functionality. Listen for * both SHADE_LOCKED and shade/qs expansion in order to determine lock state, as one can arrive @@ -218,21 +198,38 @@ constructor( ) .dumpValue("isOnLockscreenWithoutShade") + /** If the user is visually on the glanceable hub or transitioning to/from it */ + private val isOnGlanceableHub: Flow<Boolean> = + combine( + keyguardTransitionInteractor.finishedKeyguardState.map { state -> + state == GLANCEABLE_HUB + }, + keyguardTransitionInteractor + .isInTransitionWhere { from, to -> + from == GLANCEABLE_HUB || to == GLANCEABLE_HUB + } + .onStart { emit(false) } + ) { isOnGlanceableHub, transitioningToOrFromHub -> + isOnGlanceableHub || transitioningToOrFromHub + } + .distinctUntilChanged() + .dumpWhileCollecting("isOnGlanceableHub") + /** Are we purely on the glanceable hub without the shade/qs? */ val isOnGlanceableHubWithoutShade: Flow<Boolean> = combine( - communalInteractor.isIdleOnCommunal, + isOnGlanceableHub, // Shade with notifications shadeInteractor.shadeExpansion.map { it > 0f }, // Shade without notifications, quick settings only (pull down from very top on // lockscreen) shadeInteractor.qsExpansion.map { it > 0f }, - ) { isIdleOnCommunal, isShadeVisible, qsExpansion -> - isIdleOnCommunal && !(isShadeVisible || qsExpansion) + ) { isGlanceableHub, isShadeVisible, qsExpansion -> + isGlanceableHub && !(isShadeVisible || qsExpansion) } .stateIn( scope = applicationScope, - started = SharingStarted.WhileSubscribed(), + started = SharingStarted.Eagerly, initialValue = false, ) .dumpWhileCollecting("isOnGlanceableHubWithoutShade") @@ -431,39 +428,35 @@ constructor( } /** - * Returns a flow of the expected alpha while running a LOCKSCREEN<->GLANCEABLE_HUB transition - * or idle on the glanceable hub. + * Returns a flow of the expected alpha while running a LOCKSCREEN<->GLANCEABLE_HUB or + * DREAMING<->GLANCEABLE_HUB transition or idle on the hub. * * Must return 1.0f when not controlling the alpha since notifications does a min of all the * alpha sources. */ val glanceableHubAlpha: Flow<Float> = - isOnGlanceableHubWithoutShade - .flatMapLatest { isOnGlanceableHubWithoutShade -> - combineTransform( - lockscreenToGlanceableHubRunning, - glanceableHubToLockscreenRunning, - merge( - lockscreenToGlanceableHubTransitionViewModel.notificationAlpha, - glanceableHubToLockscreenTransitionViewModel.notificationAlpha, - ) - ) { lockscreenToGlanceableHubRunning, glanceableHubToLockscreenRunning, alpha -> - if (isOnGlanceableHubWithoutShade) { - // Notifications should not be visible on the glanceable hub. - // TODO(b/321075734): implement a way to actually set the notifications to - // gone - // while on the hub instead of just adjusting alpha - emit(0f) - } else if ( - lockscreenToGlanceableHubRunning || glanceableHubToLockscreenRunning - ) { - emit(alpha) - } else { - // Not on the hub and no transitions running, return full visibility so we - // don't - // block the notifications from showing. - emit(1f) - } + combineTransform( + isOnGlanceableHubWithoutShade, + isOnLockscreen, + merge( + lockscreenToGlanceableHubTransitionViewModel.notificationAlpha, + glanceableHubToLockscreenTransitionViewModel.notificationAlpha, + ) + ) { isOnGlanceableHubWithoutShade, isOnLockscreen, alpha, + -> + if (isOnGlanceableHubWithoutShade && !isOnLockscreen) { + // Notifications should not be visible on the glanceable hub. + // TODO(b/321075734): implement a way to actually set the notifications to + // gone while on the hub instead of just adjusting alpha + emit(0f) + } else if (isOnGlanceableHubWithoutShade) { + // We are transitioning between hub and lockscreen, so set the alpha for the + // transition animation. + emit(alpha) + } else { + // Not on the hub and no transitions running, return full visibility so we + // don't block the notifications from showing. + emit(1f) } } .dumpWhileCollecting("glanceableHubAlpha") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt index 23a080b7b931..a55de251314f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt @@ -648,6 +648,10 @@ constructor( } if (intent.isActivity) { assistManagerLazy.get().hideAssist() + // This activity could have started while the device is dreaming, in which case + // the dream would occlude the activity. In order to show the newly started + // activity, we wake from the dream. + keyguardUpdateMonitor.awakenFromDream() } intentSentUiThreadCallback?.let { postOnUiThread(runnable = it) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java index e2e13a192429..ab9ecab8e0c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java @@ -313,7 +313,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba mHeadsUpManager.unpinAll(true /* userUnpinned */); mMetricsLogger.count("panel_open", 1); } else if (!mQsController.getExpanded() - && !mShadeViewController.isExpandingOrCollapsing()) { + && !mShadeController.isExpandingOrCollapsing()) { mShadeController.animateExpandQs(); mMetricsLogger.count("panel_open_qs", 1); } 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 ba89d4ac22cd..7dd328a4aad0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -579,7 +579,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb final boolean hideBouncerOverDream = mDreamOverlayStateController.isOverlayActive() && (mShadeLockscreenInteractor.isExpanded() - || mShadeLockscreenInteractor.isExpandingOrCollapsing()); + || mShadeController.get().isExpandingOrCollapsing()); final boolean isUserTrackingStarted = event.getFraction() != EXPANSION_HIDDEN && event.getTracking(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 5bced934be7a..9633cb085afa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -135,7 +135,6 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene @Nullable private RevealParams mRevealParams; private Rect mContentBackgroundBounds; - private boolean mIsFocusAnimationFlagActive; private boolean mIsAnimatingAppearance = false; // TODO(b/193539698): move these to a Controller @@ -432,7 +431,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene // case to prevent flicker. if (!mRemoved) { ViewGroup parent = (ViewGroup) getParent(); - if (animate && parent != null && mIsFocusAnimationFlagActive) { + if (animate && parent != null) { ViewGroup grandParent = (ViewGroup) parent.getParent(); View actionsContainer = getActionsContainerLayout(); @@ -497,8 +496,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } private void setTopMargin(int topMargin) { - if (!(getLayoutParams() instanceof FrameLayout.LayoutParams)) return; - final FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams(); + if (!(getLayoutParams() instanceof FrameLayout.LayoutParams layoutParams)) return; layoutParams.topMargin = topMargin; setLayoutParams(layoutParams); } @@ -608,24 +606,10 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } /** - * Sets whether the feature flag for the revised inline reply animation is active or not. - * @param active - */ - public void setIsFocusAnimationFlagActive(boolean active) { - mIsFocusAnimationFlagActive = active; - } - - /** * Focuses the RemoteInputView and animates its appearance */ public void focusAnimated() { - if (!mIsFocusAnimationFlagActive && getVisibility() != VISIBLE - && mRevealParams != null) { - android.animation.Animator animator = mRevealParams.createCircularRevealAnimator(this); - animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); - animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); - animator.start(); - } else if (mIsFocusAnimationFlagActive && getVisibility() != VISIBLE) { + if (getVisibility() != VISIBLE) { mIsAnimatingAppearance = true; setAlpha(0f); Animator focusAnimator = getFocusAnimator(getActionsContainerLayout()); @@ -680,37 +664,19 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } private void reset() { - if (mIsFocusAnimationFlagActive) { - mProgressBar.setVisibility(INVISIBLE); - mResetting = true; - mSending = false; - mController.removeSpinning(mEntry.getKey(), mToken); - onDefocus(true /* animate */, false /* logClose */, () -> { - mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText()); - mEditText.getText().clear(); - mEditText.setEnabled(isAggregatedVisible()); - mSendButton.setVisibility(VISIBLE); - updateSendButton(); - setAttachment(null); - mResetting = false; - }); - return; - } - + mProgressBar.setVisibility(INVISIBLE); mResetting = true; mSending = false; - mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText()); - - mEditText.getText().clear(); - mEditText.setEnabled(isAggregatedVisible()); - mSendButton.setVisibility(VISIBLE); - mProgressBar.setVisibility(INVISIBLE); mController.removeSpinning(mEntry.getKey(), mToken); - updateSendButton(); - onDefocus(false /* animate */, false /* logClose */, null /* doAfterDefocus */); - setAttachment(null); - - mResetting = false; + onDefocus(true /* animate */, false /* logClose */, () -> { + mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText()); + mEditText.getText().clear(); + mEditText.setEnabled(isAggregatedVisible()); + mSendButton.setVisibility(VISIBLE); + updateSendButton(); + setAttachment(null); + mResetting = false; + }); } @Override @@ -854,7 +820,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); - if (mIsFocusAnimationFlagActive) setPivotY(getMeasuredHeight()); + setPivotY(getMeasuredHeight()); if (mContentBackgroundBounds != null) { mContentBackground.setBounds(mContentBackgroundBounds); } @@ -1015,9 +981,9 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene private RemoteInputView mRemoteInputView; boolean mShowImeOnInputConnection; - private LightBarController mLightBarController; + private final LightBarController mLightBarController; private InputMethodManager mInputMethodManager; - private ArraySet<String> mSupportedMimes = new ArraySet<>(); + private final ArraySet<String> mSupportedMimes = new ArraySet<>(); UserHandle mUser; public RemoteEditText(Context context, AttributeSet attrs) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt index 6c0d43394074..bfee9adf1f15 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt @@ -32,7 +32,6 @@ import android.view.View import com.android.internal.logging.UiEventLogger import com.android.systemui.res.R import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags.NOTIFICATION_INLINE_REPLY_ANIMATION import com.android.systemui.statusbar.NotificationRemoteInputManager import com.android.systemui.statusbar.RemoteInputController import com.android.systemui.statusbar.notification.collection.NotificationEntry @@ -64,8 +63,6 @@ interface RemoteInputViewController { var revealParams: RevealParams? - val isFocusAnimationFlagActive: Boolean - /** * Sets the smart reply that should be inserted in the remote input, or `null` if the user is * not editing a smart reply. @@ -155,9 +152,6 @@ class RemoteInputViewControllerImpl @Inject constructor( override val isActive: Boolean get() = view.isActive - override val isFocusAnimationFlagActive: Boolean - get() = mFlags.isEnabled(NOTIFICATION_INLINE_REPLY_ANIMATION) - override fun bind() { if (isBound) return isBound = true @@ -168,7 +162,6 @@ class RemoteInputViewControllerImpl @Inject constructor( view.setSupportedMimeTypes(it.allowedDataTypes) } view.setRevealParameters(revealParams) - view.setIsFocusAnimationFlagActive(isFocusAnimationFlagActive) view.addOnEditTextFocusChangedListener(onFocusChangeListener) view.addOnSendRemoteInputListener(onSendRemoteInputListener) diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt index 5c53ff98b777..ac1d2803835a 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt @@ -16,6 +16,8 @@ package com.android.systemui.unfold +import android.animation.Animator +import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.annotation.BinderThread import android.content.Context @@ -23,7 +25,6 @@ import android.os.Handler import android.os.SystemProperties import android.util.Log import android.view.animation.DecelerateInterpolator -import androidx.core.animation.addListener import com.android.internal.foldables.FoldLockSettingAvailabilityProvider import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.display.data.repository.DeviceStateRepository @@ -36,17 +37,25 @@ import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Comp import com.android.systemui.unfold.dagger.UnfoldBg import com.android.systemui.util.animation.data.repository.AnimationStatusRepository import javax.inject.Inject +import kotlin.coroutines.resume import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.android.asCoroutineDispatcher +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeout +@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) class FoldLightRevealOverlayAnimation @Inject constructor( @@ -61,6 +70,9 @@ constructor( private val revealProgressValueAnimator: ValueAnimator = ValueAnimator.ofFloat(ALPHA_OPAQUE, ALPHA_TRANSPARENT) + private val areAnimationEnabled: Flow<Boolean> + get() = animationStatusRepository.areAnimationsEnabled() + private lateinit var controller: FullscreenLightRevealAnimationController @Volatile private var readyCallback: CompletableDeferred<Runnable>? = null @@ -89,33 +101,31 @@ constructor( applicationScope.launch(bgHandler.asCoroutineDispatcher()) { deviceStateRepository.state - .map { it != DeviceStateRepository.DeviceState.FOLDED } + .map { it == DeviceStateRepository.DeviceState.FOLDED } .distinctUntilChanged() - .filter { isUnfolded -> isUnfolded } - .collect { controller.ensureOverlayRemoved() } - } - - applicationScope.launch(bgHandler.asCoroutineDispatcher()) { - deviceStateRepository.state - .filter { - animationStatusRepository.areAnimationsEnabled().first() && - it == DeviceStateRepository.DeviceState.FOLDED - } - .collect { - try { - withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) { - readyCallback = CompletableDeferred() - val onReady = readyCallback?.await() - readyCallback = null - controller.addOverlay(ALPHA_OPAQUE, onReady) - waitForScreenTurnedOn() + .flatMapLatest { isFolded -> + flow<Nothing> { + if (!areAnimationEnabled.first() || !isFolded) { + return@flow + } + withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) { + readyCallback = CompletableDeferred() + val onReady = readyCallback?.await() + readyCallback = null + controller.addOverlay(ALPHA_OPAQUE, onReady) + waitForScreenTurnedOn() + } playFoldLightRevealOverlayAnimation() } - } catch (e: TimeoutCancellationException) { - Log.e(TAG, "Fold light reveal animation timed out") - ensureOverlayRemovedInternal() - } + .catchTimeoutAndLog() + .onCompletion { + controller.ensureOverlayRemoved() + val onReady = readyCallback?.takeIf { it.isCompleted }?.getCompleted() + onReady?.run() + readyCallback = null + } } + .collect {} } } @@ -128,19 +138,34 @@ constructor( powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first() } - private fun ensureOverlayRemovedInternal() { - revealProgressValueAnimator.cancel() - controller.ensureOverlayRemoved() - } - - private fun playFoldLightRevealOverlayAnimation() { + private suspend fun playFoldLightRevealOverlayAnimation() { revealProgressValueAnimator.duration = ANIMATION_DURATION revealProgressValueAnimator.interpolator = DecelerateInterpolator() revealProgressValueAnimator.addUpdateListener { animation -> controller.updateRevealAmount(animation.animatedFraction) } - revealProgressValueAnimator.addListener(onEnd = { controller.ensureOverlayRemoved() }) - revealProgressValueAnimator.start() + revealProgressValueAnimator.startAndAwaitCompletion() + } + + private suspend fun ValueAnimator.startAndAwaitCompletion(): Unit = + suspendCancellableCoroutine { continuation -> + val listener = + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + continuation.resume(Unit) + removeListener(this) + } + } + addListener(listener) + continuation.invokeOnCancellation { removeListener(listener) } + start() + } + + private fun <T> Flow<T>.catchTimeoutAndLog() = catch { exception -> + when (exception) { + is TimeoutCancellationException -> Log.e(TAG, "Fold light reveal animation timed out") + else -> throw exception + } } private companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java index dd428f562bd1..ccdcee5e0318 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java @@ -32,7 +32,6 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.animation.AnimatorTestRule; import com.android.systemui.flags.FakeFeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; @@ -57,7 +56,6 @@ public class ExpandHelperTest extends SysuiTestCase { @Before public void setUp() throws Exception { - mFeatureFlags.setDefault(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION); mDependency.injectMockDependency(KeyguardUpdateMonitor.class); mDependency.injectMockDependency(NotificationMediaManager.class); allowTestableLooperAsMainThread(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt index e7963031411d..701b7039a1ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt @@ -401,7 +401,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() { LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to Pair("Enter PIN", "PIN is required after lockdown"), LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to - Pair("Enter PIN", "Update will install when device not in use"), + Pair("Enter PIN", "PIN required for additional security"), LockPatternUtils.StrongAuthTracker .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to Pair( @@ -439,7 +439,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() { LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to Pair("Enter PIN", "PIN is required after lockdown"), LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to - Pair("Enter PIN", "Update will install when device not in use"), + Pair("Enter PIN", "PIN required for additional security"), LockPatternUtils.StrongAuthTracker .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to Pair( @@ -481,7 +481,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() { LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to Pair("Enter PIN", "PIN is required after lockdown"), LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to - Pair("Enter PIN", "Update will install when device not in use"), + Pair("Enter PIN", "PIN required for additional security"), LockPatternUtils.StrongAuthTracker .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to Pair( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 0bd4cbec64dd..184924596341 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -93,11 +93,11 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.dreams.ui.viewmodel.DreamViewModel; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.SystemPropertiesHelper; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; -import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.log.SessionTracker; import com.android.systemui.navigationbar.NavigationModeController; @@ -220,7 +220,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private boolean mKeyguardGoingAway = false; private @Mock CoroutineDispatcher mDispatcher; - private @Mock DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel; + private @Mock DreamViewModel mDreamViewModel; private @Mock SystemPropertiesHelper mSystemPropertiesHelper; private @Mock SceneContainerFlags mSceneContainerFlags; @@ -241,9 +241,9 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { final ViewRootImpl testViewRoot = mock(ViewRootImpl.class); when(testViewRoot.getView()).thenReturn(mock(View.class)); when(mStatusBarKeyguardViewManager.getViewRootImpl()).thenReturn(testViewRoot); - when(mDreamingToLockscreenTransitionViewModel.getDreamOverlayAlpha()) + when(mDreamViewModel.getDreamAlpha()) .thenReturn(mock(Flow.class)); - when(mDreamingToLockscreenTransitionViewModel.getTransitionEnded()) + when(mDreamViewModel.getTransitionEnded()) .thenReturn(mock(Flow.class)); when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mDefaultUserId); when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mDefaultUserId); @@ -1259,7 +1259,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mSystemSettings, mSystemClock, mDispatcher, - () -> mDreamingToLockscreenTransitionViewModel, + () -> mDreamViewModel, mSystemPropertiesHelper, () -> mock(WindowManagerLockscreenVisibilityManager.class), mSelectedUserInteractor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt index 22a2e93eb10d..f252163ee332 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt @@ -130,6 +130,24 @@ class KeyguardClockViewModelTest : SysuiTestCase() { assertThat(value()).isEqualTo(LARGE) } + @Test + fun isLargeClockVisible_whenLargeClockSize_isTrue() = + scope.runTest { + fakeSettings.putInt(LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1) + keyguardClockRepository.setClockSize(LARGE) + var value = collectLastValue(underTest.isLargeClockVisible) + assertThat(value()).isEqualTo(true) + } + + @Test + fun isLargeClockVisible_whenSmallClockSize_isFalse() = + scope.runTest { + fakeSettings.putInt(LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1) + keyguardClockRepository.setClockSize(SMALL) + var value = collectLastValue(underTest.isLargeClockVisible) + assertThat(value()).isEqualTo(false) + } + private fun setupMockClock() { whenever(clock.largeClock).thenReturn(largeClock) whenever(largeClock.config).thenReturn(clockFaceConfig) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt index bcec6109faf6..b80dcd41d53e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt @@ -62,6 +62,7 @@ import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth import kotlin.math.min +import kotlin.test.assertEquals import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.emptyFlow @@ -77,7 +78,6 @@ import org.mockito.ArgumentMatchers import org.mockito.Mock import org.mockito.Mockito import org.mockito.MockitoAnnotations -import kotlin.test.assertEquals @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -134,7 +134,12 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { private lateinit var lockscreenToPrimaryBouncerTransitionViewModel: LockscreenToPrimaryBouncerTransitionViewModel @Mock - private lateinit var transitionInteractor: KeyguardTransitionInteractor + private lateinit var lockscreenToGlanceableHubTransitionViewModel: + LockscreenToGlanceableHubTransitionViewModel + @Mock + private lateinit var glanceableHubToLockscreenTransitionViewModel: + GlanceableHubToLockscreenTransitionViewModel + @Mock private lateinit var transitionInteractor: KeyguardTransitionInteractor private lateinit var underTest: KeyguardQuickAffordancesCombinedViewModel @@ -271,6 +276,10 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { whenever(lockscreenToOccludedTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow()) whenever(lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha) .thenReturn(emptyFlow()) + whenever(lockscreenToGlanceableHubTransitionViewModel.shortcutsAlpha) + .thenReturn(emptyFlow()) + whenever(glanceableHubToLockscreenTransitionViewModel.shortcutsAlpha) + .thenReturn(emptyFlow()) whenever(shadeInteractor.anyExpansion).thenReturn(intendedShadeAlphaMutableStateFlow) whenever(transitionInteractor.finishedKeyguardState) .thenReturn(intendedFinishedKeyguardStateFlow) @@ -307,6 +316,8 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { offToLockscreenTransitionViewModel = offToLockscreenTransitionViewModel, primaryBouncerToLockscreenTransitionViewModel = primaryBouncerToLockscreenTransitionViewModel, + glanceableHubToLockscreenTransitionViewModel = + glanceableHubToLockscreenTransitionViewModel, lockscreenToAodTransitionViewModel = lockscreenToAodTransitionViewModel, lockscreenToDozingTransitionViewModel = lockscreenToDozingTransitionViewModel, lockscreenToDreamingHostedTransitionViewModel = @@ -316,6 +327,8 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel, lockscreenToPrimaryBouncerTransitionViewModel = lockscreenToPrimaryBouncerTransitionViewModel, + lockscreenToGlanceableHubTransitionViewModel = + lockscreenToGlanceableHubTransitionViewModel, transitionInteractor = transitionInteractor, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt index 3dc90374206a..0baee5dd0e9d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt @@ -274,8 +274,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) screenshotExecutor.onCloseSystemDialogsReceived() - verify(controller0).dismissScreenshot(any()) - verify(controller1).dismissScreenshot(any()) + verify(controller0).requestDismissal(any()) + verify(controller1).requestDismissal(any()) screenshotExecutor.onDestroy() } @@ -290,8 +290,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) screenshotExecutor.onCloseSystemDialogsReceived() - verify(controller0, never()).dismissScreenshot(any()) - verify(controller1).dismissScreenshot(any()) + verify(controller0, never()).requestDismissal(any()) + verify(controller1).requestDismissal(any()) screenshotExecutor.onDestroy() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index 42a6924b95e1..b114e13bb25c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -60,7 +60,6 @@ import com.android.internal.widget.CachingIconView; import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestableContext; import com.android.systemui.flags.FakeFeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.AboveShelfChangedListener; @@ -104,7 +103,6 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { TestableLooper.get(this), mFeatureFlags); mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL); - mFeatureFlags.setDefault(Flags.SENSITIVE_REVEAL_ANIM); } @Test @@ -186,14 +184,6 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - public void testSetSensitiveOnNotifRowNotifiesOfHeightChange_withOtherFlagValue() - throws Exception { - FakeFeatureFlags flags = mFeatureFlags; - flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM)); - testSetSensitiveOnNotifRowNotifiesOfHeightChange(); - } - - @Test public void testSetSensitiveOnNotifRowNotifiesOfHeightChange() throws Exception { // GIVEN a sensitive notification row that's currently redacted ExpandableNotificationRow row = mNotificationTestHelper.createRow(); @@ -210,19 +200,10 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { // WHEN the row is set to no longer be sensitive row.setSensitive(false, true); - boolean expectAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM); // VERIFY that the height change listener is invoked assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPrivateLayout()); assertThat(row.getIntrinsicHeight()).isGreaterThan(0); - verify(listener).onHeightChanged(eq(row), eq(expectAnimation)); - } - - @Test - public void testSetSensitiveOnGroupRowNotifiesOfHeightChange_withOtherFlagValue() - throws Exception { - FakeFeatureFlags flags = mFeatureFlags; - flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM)); - testSetSensitiveOnGroupRowNotifiesOfHeightChange(); + verify(listener).onHeightChanged(eq(row), eq(true)); } @Test @@ -242,19 +223,10 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { // WHEN the row is set to no longer be sensitive group.setSensitive(false, true); - boolean expectAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM); // VERIFY that the height change listener is invoked assertThat(group.getShowingLayout()).isSameInstanceAs(group.getPrivateLayout()); assertThat(group.getIntrinsicHeight()).isGreaterThan(0); - verify(listener).onHeightChanged(eq(group), eq(expectAnimation)); - } - - @Test - public void testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange_withOtherFlagValue() - throws Exception { - FakeFeatureFlags flags = mFeatureFlags; - flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM)); - testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange(); + verify(listener).onHeightChanged(eq(group), eq(true)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt index b938029cc6b8..9a7b8ec2ec07 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt @@ -10,7 +10,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ShadeInterpolation import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.res.R import com.android.systemui.shade.transition.LargeScreenShadeInterpolator import com.android.systemui.statusbar.NotificationShelf @@ -23,7 +22,6 @@ import com.android.systemui.util.mockito.mock import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue -import org.junit.Assume.assumeTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -38,7 +36,6 @@ import org.mockito.Mockito.`when` as whenever @RunWithLooper open class NotificationShelfTest : SysuiTestCase() { - open val useSensitiveReveal: Boolean = false private val flags = FakeFeatureFlags() @Mock private lateinit var largeScreenShadeInterpolator: LargeScreenShadeInterpolator @@ -53,7 +50,6 @@ open class NotificationShelfTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) mDependency.injectTestDependency(FeatureFlags::class.java, flags) - flags.set(Flags.SENSITIVE_REVEAL_ANIM, useSensitiveReveal) val root = FrameLayout(context) shelf = LayoutInflater.from(root.context) @@ -335,7 +331,6 @@ open class NotificationShelfTest : SysuiTestCase() { @Test fun updateState_withNullLastVisibleBackgroundChild_hideShelf() { // GIVEN - assumeTrue(useSensitiveReveal) whenever(ambientState.stackY).thenReturn(100f) whenever(ambientState.stackHeight).thenReturn(100f) val paddingBetweenElements = @@ -362,7 +357,6 @@ open class NotificationShelfTest : SysuiTestCase() { @Test fun updateState_withNullFirstViewInShelf_hideShelf() { // GIVEN - assumeTrue(useSensitiveReveal) whenever(ambientState.stackY).thenReturn(100f) whenever(ambientState.stackHeight).thenReturn(100f) val paddingBetweenElements = @@ -389,7 +383,6 @@ open class NotificationShelfTest : SysuiTestCase() { @Test fun updateState_withCollapsedShade_hideShelf() { // GIVEN - assumeTrue(useSensitiveReveal) whenever(ambientState.stackY).thenReturn(100f) whenever(ambientState.stackHeight).thenReturn(100f) val paddingBetweenElements = @@ -416,7 +409,6 @@ open class NotificationShelfTest : SysuiTestCase() { @Test fun updateState_withHiddenSectionBeforeShelf_hideShelf() { // GIVEN - assumeTrue(useSensitiveReveal) whenever(ambientState.stackY).thenReturn(100f) whenever(ambientState.stackHeight).thenReturn(100f) val paddingBetweenElements = @@ -476,10 +468,3 @@ open class NotificationShelfTest : SysuiTestCase() { assertEquals(expectedAlpha, shelf.viewState.alpha) } } - -@SmallTest -@RunWith(AndroidTestingRunner::class) -@RunWithLooper -class NotificationShelfWithSensitiveRevealTest : NotificationShelfTest() { - override val useSensitiveReveal: Boolean = true -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 220305cc6bda..13df09134b23 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -165,8 +165,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { // TODO: Ideally we wouldn't need to set these unless a test actually reads them, // and then we would test both configurations, but currently they are all read // in the constructor. - mFeatureFlags.setDefault(Flags.SENSITIVE_REVEAL_ANIM); - mFeatureFlags.setDefault(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS); mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION); mFeatureFlags.setDefault(Flags.UNCLEARED_TRANSIENT_HUN_FIX); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index 56fc7b9d818f..1748cffcddda 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java @@ -22,6 +22,7 @@ import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; +import static com.android.systemui.Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND; import static com.google.common.truth.Truth.assertThat; @@ -38,6 +39,8 @@ import static org.mockito.Mockito.when; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -226,9 +229,11 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { } @Test - public void onViewAttached_callbacksRegistered() { + @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND) + public void onViewAttached_updateUserSwitcherFlagEnabled_callbacksRegistered() { mController.onViewAttached(); + runAllScheduled(); verify(mConfigurationController).addCallback(any()); verify(mAnimationScheduler).addCallback(any()); verify(mUserInfoController).addCallback(any()); @@ -238,7 +243,39 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { } @Test - public void onConfigurationChanged_updatesUserSwitcherVisibility() { + @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND) + public void onViewAttached_updateUserSwitcherFlagDisabled_callbacksRegistered() { + mController.onViewAttached(); + + verify(mConfigurationController).addCallback(any()); + verify(mAnimationScheduler).addCallback(any()); + verify(mUserInfoController).addCallback(any()); + verify(mCommandQueue).addCallback(any()); + verify(mStatusBarIconController).addIconGroup(any()); + verify(mUserManager).isUserSwitcherEnabled(anyBoolean()); + } + + @Test + @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND) + public void + onConfigurationChanged_updateUserSwitcherFlagEnabled_updatesUserSwitcherVisibility() { + mController.onViewAttached(); + runAllScheduled(); + verify(mConfigurationController).addCallback(mConfigurationListenerCaptor.capture()); + clearInvocations(mUserManager); + clearInvocations(mKeyguardStatusBarView); + + mConfigurationListenerCaptor.getValue().onConfigChanged(null); + + runAllScheduled(); + verify(mUserManager).isUserSwitcherEnabled(anyBoolean()); + verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean()); + } + + @Test + @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND) + public void + onConfigurationChanged_updateUserSwitcherFlagDisabled_updatesUserSwitcherVisibility() { mController.onViewAttached(); verify(mConfigurationController).addCallback(mConfigurationListenerCaptor.capture()); clearInvocations(mUserManager); @@ -250,7 +287,26 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { } @Test - public void onKeyguardVisibilityChanged_updatesUserSwitcherVisibility() { + @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND) + public void + onKeyguardVisibilityChanged_userSwitcherFlagEnabled_updatesUserSwitcherVisibility() { + mController.onViewAttached(); + runAllScheduled(); + verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture()); + clearInvocations(mUserManager); + clearInvocations(mKeyguardStatusBarView); + + mKeyguardCallbackCaptor.getValue().onKeyguardVisibilityChanged(true); + + runAllScheduled(); + verify(mUserManager).isUserSwitcherEnabled(anyBoolean()); + verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean()); + } + + @Test + @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND) + public void + onKeyguardVisibilityChanged_userSwitcherFlagDisabled_updatesUserSwitcherVisibility() { mController.onViewAttached(); verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture()); clearInvocations(mUserManager); @@ -298,7 +354,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { verify(mStatusBarIconController).addIconGroup(any()); } - + @Test public void setBatteryListening_true_callbackAdded() { mController.setBatteryListening(true); @@ -762,6 +818,11 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { return captor.getValue(); } + private void runAllScheduled() { + mBackgroundExecutor.runAllReady(); + mFakeExecutor.runAllReady(); + } + private static class TestShadeViewStateProvider implements ShadeViewStateProvider { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java index 13167b2d281f..c25978271b17 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java @@ -71,7 +71,6 @@ import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.AnimatorTestRule; import com.android.systemui.flags.FakeFeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -455,7 +454,6 @@ public class RemoteInputViewTest extends SysuiTestCase { private RemoteInputViewController bindController( RemoteInputView view, NotificationEntry entry) { - mFeatureFlags.set(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION, true); RemoteInputViewControllerImpl viewController = new RemoteInputViewControllerImpl( view, entry, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt index 161c6fb9f134..8866fd31faac 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt @@ -16,6 +16,8 @@ package com.android.systemui.communal.domain.interactor +import android.os.userManager +import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.communal.data.repository.communalMediaRepository import com.android.systemui.communal.data.repository.communalPrefsRepository import com.android.systemui.communal.data.repository.communalRepository @@ -40,6 +42,7 @@ import com.android.systemui.util.mockito.mock val Kosmos.communalInteractor by Fixture { CommunalInteractor( applicationScope = applicationCoroutineScope, + broadcastDispatcher = broadcastDispatcher, communalRepository = communalRepository, widgetRepository = communalWidgetRepository, mediaRepository = communalMediaRepository, @@ -50,6 +53,7 @@ val Kosmos.communalInteractor by Fixture { editWidgetsActivityStarter = editWidgetsActivityStarter, userTracker = userTracker, activityStarter = activityStarter, + userManager = userManager, logBuffer = logcatLogBuffer("CommunalInteractor"), tableLogBuffer = mock(), communalSettingsInteractor = communalSettingsInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt index 5dd50731c58a..a45b269638c0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt @@ -19,12 +19,10 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.testDispatcher val Kosmos.glanceableHubTransitions by Kosmos.Fixture { GlanceableHubTransitions( - bgDispatcher = testDispatcher, transitionRepository = keyguardTransitionRepository, transitionInteractor = keyguardTransitionInteractor, communalInteractor = communalInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelKosmos.kt index 00741eb69c62..298c70de47ee 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.common.ui.domain.interactor.configurationInteractor +import com.android.systemui.keyguard.domain.interactor.fromDreamingTransitionInteractor import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos @@ -25,5 +26,6 @@ val Kosmos.dreamingToGlanceableHubTransitionViewModel by DreamingToGlanceableHubTransitionViewModel( configurationInteractor = configurationInteractor, animationFlow = keyguardTransitionAnimationFlow, + fromDreamingTransitionInteractor = fromDreamingTransitionInteractor ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt index 5f70a2f06f2e..450dcc25c903 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt @@ -16,7 +16,6 @@ package com.android.systemui.keyguard.ui.viewmodel -import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture @@ -26,7 +25,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi @ExperimentalCoroutinesApi val Kosmos.dreamingToLockscreenTransitionViewModel by Fixture { DreamingToLockscreenTransitionViewModel( - keyguardTransitionInteractor = keyguardTransitionInteractor, fromDreamingTransitionInteractor = mock(), animationFlow = keyguardTransitionAnimationFlow, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt index 6b604e116c91..728c67af5e06 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt @@ -18,6 +18,7 @@ package com.android.systemui.shade.data.repository import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.shade.shared.model.ShadeMode import dagger.Binds import dagger.Module import javax.inject.Inject @@ -60,8 +61,8 @@ class FakeShadeRepository @Inject constructor() : ShadeRepository { override val legacyLockscreenShadeTracking = MutableStateFlow(false) - private val _isSplitShade = MutableStateFlow(false) - override val isSplitShade: StateFlow<Boolean> = _isSplitShade.asStateFlow() + private val _shadeMode = MutableStateFlow<ShadeMode>(ShadeMode.Single) + override val shadeMode: StateFlow<ShadeMode> = _shadeMode.asStateFlow() @Deprecated("Use ShadeInteractor instead") override fun setLegacyIsQsExpanded(legacyIsQsExpanded: Boolean) { @@ -136,8 +137,8 @@ class FakeShadeRepository @Inject constructor() : ShadeRepository { _legacyShadeExpansion.value = expandedFraction } - override fun setSplitShade(isSplitShade: Boolean) { - _isSplitShade.value = isSplitShade + override fun setShadeMode(shadeMode: ShadeMode) { + _shadeMode.value = shadeMode } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt index c01366489c69..de0cc6590593 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel -import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.dump.dumpManager import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor @@ -56,7 +55,6 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture { keyguardInteractor = keyguardInteractor, keyguardTransitionInteractor = keyguardTransitionInteractor, shadeInteractor = shadeInteractor, - communalInteractor = communalInteractor, alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel, aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel, aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel, diff --git a/packages/Tethering/OWNERS b/packages/Tethering/OWNERS deleted file mode 100644 index aa87958f1d53..000000000000 --- a/packages/Tethering/OWNERS +++ /dev/null @@ -1 +0,0 @@ -include /services/core/java/com/android/server/net/OWNERS diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index af47ed28e3b0..73584154df3a 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -611,12 +611,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ if (svcConnTracingEnabled()) { logTraceSvcConn("getWindow", "windowId=" + windowId); } + int displayId = Display.INVALID_DISPLAY; + if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) { + displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowId( + mSystemSupport.getCurrentUserIdLocked(), windowId); + } synchronized (mLock) { - int displayId = Display.INVALID_DISPLAY; - if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) { - displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked( - mSystemSupport.getCurrentUserIdLocked(), windowId); - } ensureWindowsAvailableTimedLocked(displayId); if (!hasRightsToCurrentUserLocked()) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 3cbfd42c279d..4be303ad9e57 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -1261,15 +1261,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // the computation for performance reasons. boolean shouldComputeWindows = false; int displayId = event.getDisplayId(); + final int windowId = event.getWindowId(); + if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID + && displayId == Display.INVALID_DISPLAY) { + displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowId( + resolvedUserId, windowId); + event.setDisplayId(displayId); + } synchronized (mLock) { - final int windowId = event.getWindowId(); - if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID - && displayId == Display.INVALID_DISPLAY) { - displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked( - resolvedUserId, windowId); - event.setDisplayId(displayId); - } - if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && displayId != Display.INVALID_DISPLAY && mA11yWindowManager.isTrackingWindowsLocked(displayId)) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java index b8181505b9c4..8c06bc8f4607 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java @@ -2038,8 +2038,11 @@ public class AccessibilityWindowManager { * @param windowId The windowId * @return The display ID */ - public int getDisplayIdByUserIdAndWindowIdLocked(int userId, int windowId) { - final IBinder windowToken = getWindowTokenForUserAndWindowIdLocked(userId, windowId); + public int getDisplayIdByUserIdAndWindowId(int userId, int windowId) { + final IBinder windowToken; + synchronized (mLock) { + windowToken = getWindowTokenForUserAndWindowIdLocked(userId, windowId); + } if (traceWMEnabled()) { logTraceWM("getDisplayIdForWindow", "token=" + windowToken); } diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java index d9e25ef7dcdc..e13994e75690 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java @@ -525,8 +525,9 @@ public class TouchState { mReceivedPointersDown |= pointerFlag; mReceivedPointers[pointerId].set( event.getX(pointerIndex), event.getY(pointerIndex), event.getEventTime()); - - mPrimaryPointerId = pointerId; + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + mPrimaryPointerId = pointerId; + } } /** diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 13bc77296f0f..ca2a3ddc49bc 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -6458,12 +6458,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } else if (response != null) { if (viewId.isVirtualInt()) { ViewNode viewNode = getViewNodeFromContextsLocked(viewId); - if (viewNode != null && viewNode.getCredentialManagerCallback() != null) { + if (viewNode != null && viewNode.getPendingCredentialCallback() != null) { Bundle resultData = new Bundle(); resultData.putParcelable( CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, response); - viewNode.getCredentialManagerCallback().send(SUCCESS_CREDMAN_SELECTOR, + viewNode.getPendingCredentialCallback().send(SUCCESS_CREDMAN_SELECTOR, resultData); } else { Slog.w(TAG, "View node not found after GetCredentialResponse"); diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index b1672ed9c732..8244d20e8e6a 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -29,6 +29,7 @@ import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS; import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS; import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; +import static android.companion.virtualdevice.flags.Flags.virtualCameraServiceDiscovery; import android.annotation.EnforcePermission; import android.annotation.NonNull; @@ -111,6 +112,8 @@ import com.android.server.companion.virtual.audio.VirtualAudioController; import com.android.server.companion.virtual.camera.VirtualCameraController; import com.android.server.inputmethod.InputMethodManagerInternal; +import dalvik.annotation.optimization.FastNative; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.List; @@ -265,7 +268,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub runningAppsChangedCallback, params, DisplayManagerGlobal.getInstance(), - Flags.virtualCamera() + isVirtualCameraEnabled() ? new VirtualCameraController(params.getDevicePolicy(POLICY_TYPE_CAMERA)) : null); } @@ -1535,4 +1538,13 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub return mToken; } } + + private static boolean isVirtualCameraEnabled() { + return Flags.virtualCamera() && virtualCameraServiceDiscovery() + && nativeVirtualCameraServiceBuildFlagEnabled(); + } + + // Returns true if virtual_camera service is enabled in this build. + @FastNative + private static native boolean nativeVirtualCameraServiceBuildFlagEnabled(); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 60bfc637225f..5e6ff55f4e94 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -4755,6 +4755,7 @@ public class ActivityManagerService extends IActivityManager.Stub autofillOptions, contentCaptureOptions, app.getDisabledCompatChanges(), + app.getLoggableCompatChanges(), serializedSystemFontMap, app.getStartElapsedTime(), app.getStartUptime()); diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java index 0ce1407004f5..48daef801245 100644 --- a/services/core/java/com/android/server/am/AppProfiler.java +++ b/services/core/java/com/android/server/am/AppProfiler.java @@ -711,7 +711,7 @@ public class AppProfiler { } } if (profile != null) { - long startTime = SystemClock.currentThreadTimeMillis(); + long startTime = SystemClock.uptimeMillis(); // skip background PSS calculation under the following situations: // - app is capturing camera imagery // - app is frozen and we have already collected PSS once. @@ -721,7 +721,7 @@ public class AppProfiler { || mService.isCameraActiveForUid(profile.mApp.uid) || mService.mConstants.APP_PROFILER_PSS_PROFILING_DISABLED; long pss = skipPSSCollection ? 0 : Debug.getPss(pid, tmp, null); - long endTime = SystemClock.currentThreadTimeMillis(); + long endTime = SystemClock.uptimeMillis(); synchronized (mProfilerLock) { if (pss != 0 && profile.getThread() != null && profile.getSetProcState() == procState @@ -852,7 +852,7 @@ public class AppProfiler { } } if (profile != null) { - long startTime = SystemClock.currentThreadTimeMillis(); + long startTime = SystemClock.uptimeMillis(); // skip background RSS calculation under the following situations: // - app is capturing camera imagery // - app is frozen and we have already collected RSS once. @@ -862,7 +862,7 @@ public class AppProfiler { || mService.isCameraActiveForUid(profile.mApp.uid) || mService.mConstants.APP_PROFILER_PSS_PROFILING_DISABLED; long rss = skipRSSCollection ? 0 : Debug.getRss(pid, null); - long endTime = SystemClock.currentThreadTimeMillis(); + long endTime = SystemClock.uptimeMillis(); synchronized (mProfilerLock) { if (rss != 0 && profile.getThread() != null && profile.getSetProcState() == procState diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index cd6964ea2631..7f6d62c29648 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -1051,7 +1051,7 @@ public class OomAdjuster { assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP()); - postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime); + postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime, true); if (startProfiling) { mService.mOomAdjProfiler.oomAdjEnded(); @@ -1073,12 +1073,12 @@ public class OomAdjuster { @GuardedBy({"mService", "mProcLock"}) protected void postUpdateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, ActiveUids activeUids, - long now, long nowElapsed, long oldTime) { + long now, long nowElapsed, long oldTime, boolean doingAll) { mNumNonCachedProcs = 0; mNumCachedHiddenProcs = 0; final boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids, - oomAdjReason); + oomAdjReason, doingAll); mNumServiceProcs = mNewNumServiceProcs; if (mService.mAlwaysFinishActivities) { @@ -1288,7 +1288,8 @@ public class OomAdjuster { @GuardedBy({"mService", "mProcLock"}) private boolean updateAndTrimProcessLSP(final long now, final long nowElapsed, - final long oldTime, final ActiveUids activeUids, @OomAdjReason int oomAdjReason) { + final long oldTime, final ActiveUids activeUids, @OomAdjReason int oomAdjReason, + boolean doingAll) { ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP(); final int numLru = lruList.size(); @@ -1321,7 +1322,7 @@ public class OomAdjuster { if (!app.isKilledByAm() && app.getThread() != null) { // We don't need to apply the update for the process which didn't get computed if (state.getCompletedAdjSeq() == mAdjSeq) { - applyOomAdjLSP(app, true, now, nowElapsed, oomAdjReason); + applyOomAdjLSP(app, doingAll, now, nowElapsed, oomAdjReason); } if (app.isPendingFinishAttach()) { diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java index dd75bc0442d0..46bdfe892040 100644 --- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java +++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java @@ -820,7 +820,7 @@ public class OomAdjusterModernImpl extends OomAdjuster { computeConnectionsLSP(); assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP()); - postUpdateOomAdjInnerLSP(oomAdjReason, mActiveUids, now, nowElapsed, oldTime); + postUpdateOomAdjInnerLSP(oomAdjReason, mActiveUids, now, nowElapsed, oldTime, true); } /** @@ -908,7 +908,7 @@ public class OomAdjusterModernImpl extends OomAdjuster { } } - postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime); + postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime, false); } /** diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 27d6c608cf6c..48a9d6af0df4 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -2088,8 +2088,10 @@ public final class ProcessList { + " with non-zero pid:" + app.getPid()); } app.setDisabledCompatChanges(null); + app.setLoggableCompatChanges(null); if (mPlatformCompat != null) { app.setDisabledCompatChanges(mPlatformCompat.getDisabledChanges(app.info)); + app.setLoggableCompatChanges(mPlatformCompat.getLoggableChanges(app.info)); } final long startSeq = ++mProcStartSeqCounter; app.setStartSeq(startSeq); diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 9fa3a8bf6f3b..b93908974a42 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -259,6 +259,12 @@ class ProcessRecord implements WindowProcessListener { private long[] mDisabledCompatChanges; /** + * Set of compat changes for the process that are intended to be logged to logcat. + */ + @GuardedBy("mService") + private long[] mLoggableCompatChanges; + + /** * Who is watching for the death. */ @GuardedBy("mService") @@ -935,11 +941,21 @@ class ProcessRecord implements WindowProcessListener { } @GuardedBy("mService") + long[] getLoggableCompatChanges() { + return mLoggableCompatChanges; + } + + @GuardedBy("mService") void setDisabledCompatChanges(long[] disabledCompatChanges) { mDisabledCompatChanges = disabledCompatChanges; } @GuardedBy("mService") + void setLoggableCompatChanges(long[] loggableCompatChanges) { + mLoggableCompatChanges = loggableCompatChanges; + } + + @GuardedBy("mService") void unlinkDeathRecipient() { if (mDeathRecipient != null && mThread != null) { mThread.asBinder().unlinkToDeath(mDeathRecipient, 0); diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index d1bda7991f45..7df5fdd282c3 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -170,6 +170,7 @@ public class SettingsToPropertiesMapper { "pixel_connectivity_gps", "pixel_system_sw_video", "pixel_watch", + "platform_compat", "platform_security", "pmw", "power", diff --git a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java index 03acf72725e7..d93ff9dac91f 100644 --- a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java +++ b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java @@ -82,13 +82,6 @@ public final class BroadcastRadioServiceImpl { Slogf.w(TAG, "No module %s with id %d (HAL AIDL)", name, moduleId); return; } - try { - radioModule.setInternalHalCallback(); - } catch (RemoteException ex) { - Slogf.wtf(TAG, ex, "Broadcast radio module %s with id %d (HAL AIDL) " - + "cannot register HAL callback", name, moduleId); - return; - } if (DEBUG) { Slogf.d(TAG, "Loaded broadcast radio module %s with id %d (HAL AIDL)", name, moduleId); diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java index 4b3444db38e5..cd865105c48e 100644 --- a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java +++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java @@ -246,10 +246,6 @@ final class RadioModule { return mProperties; } - void setInternalHalCallback() throws RemoteException { - mService.setTunerCallback(mHalTunerCallback); - } - TunerSession openSession(android.hardware.radio.ITunerCallback userCb) throws RemoteException { mLogger.logRadioEvent("Open TunerSession"); @@ -257,10 +253,14 @@ final class RadioModule { Boolean antennaConnected; RadioManager.ProgramInfo currentProgramInfo; synchronized (mLock) { + boolean isFirstTunerSession = mAidlTunerSessions.isEmpty(); tunerSession = new TunerSession(this, mService, userCb); mAidlTunerSessions.add(tunerSession); antennaConnected = mAntennaConnected; currentProgramInfo = mCurrentProgramInfo; + if (isFirstTunerSession) { + mService.setTunerCallback(mHalTunerCallback); + } } // Propagate state to new client. // Note: These callbacks are invoked while holding mLock to prevent race conditions @@ -284,7 +284,6 @@ final class RadioModule { synchronized (mLock) { tunerSessions = new TunerSession[mAidlTunerSessions.size()]; mAidlTunerSessions.toArray(tunerSessions); - mAidlTunerSessions.clear(); } for (TunerSession tunerSession : tunerSessions) { @@ -402,6 +401,14 @@ final class RadioModule { mAidlTunerSessions.remove(tunerSession); } onTunerSessionProgramListFilterChanged(null); + if (mAidlTunerSessions.isEmpty()) { + try { + mService.unsetTunerCallback(); + } catch (RemoteException ex) { + Slogf.wtf(TAG, ex, "Failed to unregister HAL callback for module %d", + mProperties.getId()); + } + } } // add to mHandler queue diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index 9102cfd0d426..79025d00d128 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -25,6 +25,7 @@ import android.compat.Compatibility.ChangeConfig; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.os.Build; import android.os.Environment; import android.text.TextUtils; import android.util.LongArray; @@ -72,7 +73,6 @@ import javax.xml.datatype.DatatypeConfigurationException; * been configured. */ final class CompatConfig { - private static final String TAG = "CompatConfig"; private static final String APP_COMPAT_DATA_DIR = "/data/misc/appcompat"; private static final String STATIC_OVERRIDES_PRODUCT_DIR = "/product/etc/appcompat"; @@ -149,6 +149,56 @@ final class CompatConfig { } /** + * Retrieves the set of changes that are intended to be logged. This includes changes that + * target the most recent SDK version and are not disabled. + * + * @param app the app in question + * @return a sorted long array of change IDs + */ + long[] getLoggableChanges(ApplicationInfo app) { + LongArray loggable = new LongArray(mChanges.size()); + for (CompatChange c : mChanges.values()) { + long changeId = c.getId(); + boolean isLatestSdk = isChangeTargetingLatestSdk(c, app.targetSdkVersion); + if (c.isEnabled(app, mAndroidBuildClassifier) && isLatestSdk) { + loggable.add(changeId); + } + } + final long[] sortedChanges = loggable.toArray(); + Arrays.sort(sortedChanges); + return sortedChanges; + } + + /** + * Whether the change indicated by the given changeId is targeting the latest SDK version. + * @param c the change for which to check the target SDK version + * @param appSdkVersion the target sdk version of the app + * @return true if the changeId targets the current sdk version or the current development + * version. + */ + boolean isChangeTargetingLatestSdk(CompatChange c, int appSdkVersion) { + int maxTargetSdk = maxTargetSdkForCompatChange(c) + 1; + if (maxTargetSdk <= 0) { + // No max target sdk found. + return false; + } + + return maxTargetSdk == Build.VERSION_CODES.CUR_DEVELOPMENT || maxTargetSdk == appSdkVersion; + } + + /** + * Retrieves the CompatChange associated with the given changeId. Will return null if the + * changeId is not found. Used only for performance improvement purposes, in order to reduce + * lookups. + * + * @param changeId for which to look up the CompatChange + * @return the found compat change, or null if not found. + */ + CompatChange getCompatChange(long changeId) { + return mChanges.get(changeId); + } + + /** * Looks up a change ID by name. * * @param name name of the change to look up @@ -164,7 +214,7 @@ final class CompatConfig { } /** - * Checks if a given change is enabled for a given application. + * Checks if a given change id is enabled for a given application. * * @param changeId the ID of the change in question * @param app app to check for @@ -173,6 +223,18 @@ final class CompatConfig { */ boolean isChangeEnabled(long changeId, ApplicationInfo app) { CompatChange c = mChanges.get(changeId); + return isChangeEnabled(c, app); + } + + /** + * Checks if a given change is enabled for a given application. + * + * @param c the CompatChange in question + * @param app the app to check for + * @return {@code true} if the change is enabled for this app. Also returns {@code true} if the + * change ID is not known, as unknown changes are enabled by default. + */ + boolean isChangeEnabled(CompatChange c, ApplicationInfo app) { if (c == null) { // we know nothing about this change: default behaviour is enabled. return true; @@ -301,9 +363,21 @@ final class CompatConfig { /** * Returns the maximum SDK version for which this change can be opted in (or -1 if it is not * target SDK gated). + * + * @param changeId the id of the CompatChange to check for the max target sdk */ int maxTargetSdkForChangeIdOptIn(long changeId) { CompatChange c = mChanges.get(changeId); + return maxTargetSdkForCompatChange(c); + } + + /** + * Returns the maximum SDK version for which this change can be opted in (or -1 if it is not + * target SDK gated). + * + * @param c the CompatChange to check for the max target sdk + */ + int maxTargetSdkForCompatChange(CompatChange c) { if (c != null && c.getEnableSinceTargetSdk() != -1) { return c.getEnableSinceTargetSdk() - 1; } diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index 6cca130af35d..f8fd0a0790e0 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -120,8 +120,16 @@ public class PlatformCompat extends IPlatformCompat.Stub { reportChangeInternal(changeId, uid, ChangeReporter.STATE_LOGGED); } + /** + * Report the change, but skip over the sdk target version check. This can be used to force the + * debug logs. + * + * @param changeId of the change to report + * @param uid of the user + * @param state of the change - enabled/disabled/logged + */ private void reportChangeInternal(long changeId, int uid, int state) { - mChangeReporter.reportChange(uid, changeId, state); + mChangeReporter.reportChange(uid, changeId, state, true); } @Override @@ -164,15 +172,25 @@ public class PlatformCompat extends IPlatformCompat.Stub { } /** - * Internal version of {@link #isChangeEnabled(long, ApplicationInfo)}. + * Internal version of {@link #isChangeEnabled(long, ApplicationInfo)}. If the provided appInfo + * is not null, also reports the change. + * + * @param changeId of the change to report + * @param appInfo the app to check * * <p>Does not perform costly permission check. */ public boolean isChangeEnabledInternal(long changeId, ApplicationInfo appInfo) { - boolean enabled = isChangeEnabledInternalNoLogging(changeId, appInfo); + // Fetch the CompatChange. This is done here instead of in mCompatConfig to avoid multiple + // fetches. + CompatChange c = mCompatConfig.getCompatChange(changeId); + + boolean enabled = mCompatConfig.isChangeEnabled(c, appInfo); + int state = enabled ? ChangeReporter.STATE_ENABLED : ChangeReporter.STATE_DISABLED; if (appInfo != null) { - reportChangeInternal(changeId, appInfo.uid, - enabled ? ChangeReporter.STATE_ENABLED : ChangeReporter.STATE_DISABLED); + boolean isTargetingLatestSdk = + mCompatConfig.isChangeTargetingLatestSdk(c, appInfo.targetSdkVersion); + mChangeReporter.reportChange(appInfo.uid, changeId, state, isTargetingLatestSdk); } return enabled; } @@ -399,6 +417,19 @@ public class PlatformCompat extends IPlatformCompat.Stub { } /** + * Retrieves the set of changes that should be logged for a given app. Any change ID not in the + * returned array is ignored for logging purposes. + * + * @param appInfo The app in question + * @return A sorted long array of change IDs. We use a primitive array to minimize memory + * footprint: Every app process will store this array statically so we aim to reduce + * overhead as much as possible. + */ + public long[] getLoggableChanges(ApplicationInfo appInfo) { + return mCompatConfig.getLoggableChanges(appInfo); + } + + /** * Look up a change ID by name. * * @param name Name of the change to look up diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index 8b4e1ff4981f..64cbd5488d90 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -1015,12 +1015,15 @@ public class DisplayModeDirector { // Infinity means that we want the highest possible refresh rate minRefreshRate = highestRefreshRate; - if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled) { - // The flag had been turned off, we need to restore the original value + if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled + && displayId == Display.DEFAULT_DISPLAY) { + // The flag has been turned off, we need to restore the original value. We'll + // use the peak refresh rate of the default display. Settings.System.putFloatForUser(cr, Settings.System.MIN_REFRESH_RATE, highestRefreshRate, cr.getUserId()); } } else if (mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled + && displayId == Display.DEFAULT_DISPLAY && Math.round(minRefreshRate) == Math.round(highestRefreshRate)) { // The flag has been turned on, we need to upgrade the setting Settings.System.putFloatForUser(cr, Settings.System.MIN_REFRESH_RATE, @@ -1033,12 +1036,15 @@ public class DisplayModeDirector { // Infinity means that we want the highest possible refresh rate peakRefreshRate = highestRefreshRate; - if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled) { - // The flag had been turned off, we need to restore the original value + if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled + && displayId == Display.DEFAULT_DISPLAY) { + // The flag has been turned off, we need to restore the original value. We'll + // use the peak refresh rate of the default display. Settings.System.putFloatForUser(cr, Settings.System.PEAK_REFRESH_RATE, highestRefreshRate, cr.getUserId()); } } else if (mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled + && displayId == Display.DEFAULT_DISPLAY && Math.round(peakRefreshRate) == Math.round(highestRefreshRate)) { // The flag has been turned on, we need to upgrade the setting Settings.System.putFloatForUser(cr, Settings.System.PEAK_REFRESH_RATE, diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index c6d66db3f8cd..d9970204cf66 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -982,6 +982,18 @@ public final class DreamManagerService extends SystemService { } @Override // Binder call + public boolean canStartDreaming(boolean isScreenOn) { + checkPermission(android.Manifest.permission.READ_DREAM_STATE); + + final long ident = Binder.clearCallingIdentity(); + try { + return canStartDreamingInternal(isScreenOn); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call public void testDream(int userId, ComponentName dream) { if (dream == null) { throw new IllegalArgumentException("dream must not be null"); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 996477d9c9ba..fef56610b406 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -67,7 +67,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UiThread; import android.annotation.UserIdInt; -import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -196,21 +195,16 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.security.InvalidParameterException; -import java.time.Instant; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Locale; import java.util.Objects; import java.util.OptionalInt; import java.util.WeakHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.IntConsumer; @@ -730,344 +724,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private final CopyOnWriteArrayList<InputMethodListListener> mInputMethodListListeners = new CopyOnWriteArrayList<>(); - /** - * Internal state snapshot when - * {@link IInputMethod#startInput(IInputMethod.StartInputParams)} is about to be called. - * - * <p>Calling that IPC endpoint basically means that - * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called - * back in the current IME process shortly, which will also affect what the current IME starts - * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this - * snapshot will be taken every time when {@link InputMethodManagerService} is initiating a new - * logical input session between the client application and the current IME.</p> - * - * <p>Be careful to not keep strong references to this object forever, which can prevent - * {@link StartInputInfo#mImeToken} and {@link StartInputInfo#mTargetWindow} from being GC-ed. - * </p> - */ - private static class StartInputInfo { - private static final AtomicInteger sSequenceNumber = new AtomicInteger(0); - - final int mSequenceNumber; - final long mTimestamp; - final long mWallTime; - @UserIdInt - final int mImeUserId; - @NonNull - final IBinder mImeToken; - final int mImeDisplayId; - @NonNull - final String mImeId; - @StartInputReason - final int mStartInputReason; - final boolean mRestarting; - @UserIdInt - final int mTargetUserId; - final int mTargetDisplayId; - @Nullable - final IBinder mTargetWindow; - @NonNull - final EditorInfo mEditorInfo; - @SoftInputModeFlags - final int mTargetWindowSoftInputMode; - final int mClientBindSequenceNumber; - - StartInputInfo(@UserIdInt int imeUserId, @NonNull IBinder imeToken, int imeDisplayId, - @NonNull String imeId, @StartInputReason int startInputReason, boolean restarting, - @UserIdInt int targetUserId, int targetDisplayId, @Nullable IBinder targetWindow, - @NonNull EditorInfo editorInfo, @SoftInputModeFlags int targetWindowSoftInputMode, - int clientBindSequenceNumber) { - mSequenceNumber = sSequenceNumber.getAndIncrement(); - mTimestamp = SystemClock.uptimeMillis(); - mWallTime = System.currentTimeMillis(); - mImeUserId = imeUserId; - mImeToken = imeToken; - mImeDisplayId = imeDisplayId; - mImeId = imeId; - mStartInputReason = startInputReason; - mRestarting = restarting; - mTargetUserId = targetUserId; - mTargetDisplayId = targetDisplayId; - mTargetWindow = targetWindow; - mEditorInfo = editorInfo; - mTargetWindowSoftInputMode = targetWindowSoftInputMode; - mClientBindSequenceNumber = clientBindSequenceNumber; - } - } - @GuardedBy("ImfLock.class") private final WeakHashMap<IBinder, IBinder> mImeTargetWindowMap = new WeakHashMap<>(); - @VisibleForTesting - static final class SoftInputShowHideHistory { - private final Entry[] mEntries = new Entry[16]; - private int mNextIndex = 0; - private static final AtomicInteger sSequenceNumber = new AtomicInteger(0); - - static final class Entry { - final int mSequenceNumber = sSequenceNumber.getAndIncrement(); - @Nullable - final ClientState mClientState; - @SoftInputModeFlags - final int mFocusedWindowSoftInputMode; - @SoftInputShowHideReason - final int mReason; - // The timing of handling showCurrentInputLocked() or hideCurrentInputLocked(). - final long mTimestamp; - final long mWallTime; - final boolean mInFullscreenMode; - @NonNull - final String mFocusedWindowName; - @Nullable - final EditorInfo mEditorInfo; - @NonNull - final String mRequestWindowName; - @Nullable - final String mImeControlTargetName; - @Nullable - final String mImeTargetNameFromWm; - @Nullable - final String mImeSurfaceParentName; - - Entry(ClientState client, EditorInfo editorInfo, - String focusedWindowName, @SoftInputModeFlags int softInputMode, - @SoftInputShowHideReason int reason, - boolean inFullscreenMode, String requestWindowName, - @Nullable String imeControlTargetName, @Nullable String imeTargetName, - @Nullable String imeSurfaceParentName) { - mClientState = client; - mEditorInfo = editorInfo; - mFocusedWindowName = focusedWindowName; - mFocusedWindowSoftInputMode = softInputMode; - mReason = reason; - mTimestamp = SystemClock.uptimeMillis(); - mWallTime = System.currentTimeMillis(); - mInFullscreenMode = inFullscreenMode; - mRequestWindowName = requestWindowName; - mImeControlTargetName = imeControlTargetName; - mImeTargetNameFromWm = imeTargetName; - mImeSurfaceParentName = imeSurfaceParentName; - } - } - - void addEntry(@NonNull Entry entry) { - final int index = mNextIndex; - mEntries[index] = entry; - mNextIndex = (mNextIndex + 1) % mEntries.length; - } - - void dump(@NonNull PrintWriter pw, @NonNull String prefix) { - final DateTimeFormatter formatter = - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US) - .withZone(ZoneId.systemDefault()); - - for (int i = 0; i < mEntries.length; ++i) { - final Entry entry = mEntries[(i + mNextIndex) % mEntries.length]; - if (entry == null) { - continue; - } - pw.print(prefix); - pw.println("SoftInputShowHide #" + entry.mSequenceNumber + ":"); - - pw.print(prefix); - pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime)) - + " (timestamp=" + entry.mTimestamp + ")"); - - pw.print(prefix); - pw.print(" reason=" + InputMethodDebug.softInputDisplayReasonToString( - entry.mReason)); - pw.println(" inFullscreenMode=" + entry.mInFullscreenMode); - - pw.print(prefix); - pw.println(" requestClient=" + entry.mClientState); - - pw.print(prefix); - pw.println(" focusedWindowName=" + entry.mFocusedWindowName); - - pw.print(prefix); - pw.println(" requestWindowName=" + entry.mRequestWindowName); - - pw.print(prefix); - pw.println(" imeControlTargetName=" + entry.mImeControlTargetName); - - pw.print(prefix); - pw.println(" imeTargetNameFromWm=" + entry.mImeTargetNameFromWm); - - pw.print(prefix); - pw.println(" imeSurfaceParentName=" + entry.mImeSurfaceParentName); - - pw.print(prefix); - pw.print(" editorInfo:"); - if (entry.mEditorInfo != null) { - pw.print(" inputType=" + entry.mEditorInfo.inputType); - pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions); - pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId); - } else { - pw.println(" null"); - } - - pw.print(prefix); - pw.println(" focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString( - entry.mFocusedWindowSoftInputMode)); - } - } - } - - /** - * A ring buffer to store the history of {@link StartInputInfo}. - */ - private static final class StartInputHistory { - /** - * Entry size for non low-RAM devices. - * - * <p>TODO: Consider to follow what other system services have been doing to manage - * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p> - */ - private static final int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 32; - - /** - * Entry size for low-RAM devices. - * - * <p>TODO: Consider to follow what other system services have been doing to manage - * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p> - */ - private static final int ENTRY_SIZE_FOR_LOW_RAM_DEVICE = 5; - - private static int getEntrySize() { - if (ActivityManager.isLowRamDeviceStatic()) { - return ENTRY_SIZE_FOR_LOW_RAM_DEVICE; - } else { - return ENTRY_SIZE_FOR_HIGH_RAM_DEVICE; - } - } - - /** - * Backing store for the ring buffer. - */ - private final Entry[] mEntries = new Entry[getEntrySize()]; - - /** - * An index of {@link #mEntries}, to which next {@link #addEntry(StartInputInfo)} should - * write. - */ - private int mNextIndex = 0; - - /** - * Recyclable entry to store the information in {@link StartInputInfo}. - */ - private static final class Entry { - int mSequenceNumber; - long mTimestamp; - long mWallTime; - @UserIdInt - int mImeUserId; - @NonNull - String mImeTokenString; - int mImeDisplayId; - @NonNull - String mImeId; - @StartInputReason - int mStartInputReason; - boolean mRestarting; - @UserIdInt - int mTargetUserId; - int mTargetDisplayId; - @NonNull - String mTargetWindowString; - @NonNull - EditorInfo mEditorInfo; - @SoftInputModeFlags - int mTargetWindowSoftInputMode; - int mClientBindSequenceNumber; - - Entry(@NonNull StartInputInfo original) { - set(original); - } - - void set(@NonNull StartInputInfo original) { - mSequenceNumber = original.mSequenceNumber; - mTimestamp = original.mTimestamp; - mWallTime = original.mWallTime; - mImeUserId = original.mImeUserId; - // Intentionally convert to String so as not to keep a strong reference to a Binder - // object. - mImeTokenString = String.valueOf(original.mImeToken); - mImeDisplayId = original.mImeDisplayId; - mImeId = original.mImeId; - mStartInputReason = original.mStartInputReason; - mRestarting = original.mRestarting; - mTargetUserId = original.mTargetUserId; - mTargetDisplayId = original.mTargetDisplayId; - // Intentionally convert to String so as not to keep a strong reference to a Binder - // object. - mTargetWindowString = String.valueOf(original.mTargetWindow); - mEditorInfo = original.mEditorInfo; - mTargetWindowSoftInputMode = original.mTargetWindowSoftInputMode; - mClientBindSequenceNumber = original.mClientBindSequenceNumber; - } - } - - /** - * Add a new entry and discard the oldest entry as needed. - * @param info {@link StartInputInfo} to be added. - */ - void addEntry(@NonNull StartInputInfo info) { - final int index = mNextIndex; - if (mEntries[index] == null) { - mEntries[index] = new Entry(info); - } else { - mEntries[index].set(info); - } - mNextIndex = (mNextIndex + 1) % mEntries.length; - } - - void dump(@NonNull PrintWriter pw, @NonNull String prefix) { - final DateTimeFormatter formatter = - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US) - .withZone(ZoneId.systemDefault()); - - for (int i = 0; i < mEntries.length; ++i) { - final Entry entry = mEntries[(i + mNextIndex) % mEntries.length]; - if (entry == null) { - continue; - } - pw.print(prefix); - pw.println("StartInput #" + entry.mSequenceNumber + ":"); - - pw.print(prefix); - pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime)) - + " (timestamp=" + entry.mTimestamp + ")" - + " reason=" - + InputMethodDebug.startInputReasonToString(entry.mStartInputReason) - + " restarting=" + entry.mRestarting); - - pw.print(prefix); - pw.print(" imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]"); - pw.print(" imeUserId=" + entry.mImeUserId); - pw.println(" imeDisplayId=" + entry.mImeDisplayId); - - pw.print(prefix); - pw.println(" targetWin=" + entry.mTargetWindowString - + " [" + entry.mEditorInfo.packageName + "]" - + " targetUserId=" + entry.mTargetUserId - + " targetDisplayId=" + entry.mTargetDisplayId - + " clientBindSeq=" + entry.mClientBindSequenceNumber); - - pw.print(prefix); - pw.println(" softInputMode=" + InputMethodDebug.softInputModeToString( - entry.mTargetWindowSoftInputMode)); - - pw.print(prefix); - pw.println(" inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType) - + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions) - + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId) - + " fieldName=" + entry.mEditorInfo.fieldName - + " actionId=" + entry.mEditorInfo.actionId - + " actionLabel=" + entry.mEditorInfo.actionLabel); - } - } - } - @GuardedBy("ImfLock.class") @NonNull private final StartInputHistory mStartInputHistory = new StartInputHistory(); diff --git a/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java b/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java new file mode 100644 index 000000000000..3023603dc437 --- /dev/null +++ b/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.inputmethod; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.SystemClock; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; + +import com.android.internal.inputmethod.InputMethodDebug; +import com.android.internal.inputmethod.SoftInputShowHideReason; + +import java.io.PrintWriter; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Locale; +import java.util.concurrent.atomic.AtomicInteger; + +final class SoftInputShowHideHistory { + private static final AtomicInteger sSequenceNumber = new AtomicInteger(0); + + private final Entry[] mEntries = new Entry[16]; + private int mNextIndex = 0; + + static final class Entry { + final int mSequenceNumber = sSequenceNumber.getAndIncrement(); + @Nullable + final ClientState mClientState; + @WindowManager.LayoutParams.SoftInputModeFlags + final int mFocusedWindowSoftInputMode; + @SoftInputShowHideReason + final int mReason; + // The timing of handling showCurrentInputLocked() or hideCurrentInputLocked(). + final long mTimestamp; + final long mWallTime; + final boolean mInFullscreenMode; + @NonNull + final String mFocusedWindowName; + @Nullable + final EditorInfo mEditorInfo; + @NonNull + final String mRequestWindowName; + @Nullable + final String mImeControlTargetName; + @Nullable + final String mImeTargetNameFromWm; + @Nullable + final String mImeSurfaceParentName; + + Entry(ClientState client, EditorInfo editorInfo, + String focusedWindowName, + @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, + @SoftInputShowHideReason int reason, + boolean inFullscreenMode, String requestWindowName, + @Nullable String imeControlTargetName, @Nullable String imeTargetName, + @Nullable String imeSurfaceParentName) { + mClientState = client; + mEditorInfo = editorInfo; + mFocusedWindowName = focusedWindowName; + mFocusedWindowSoftInputMode = softInputMode; + mReason = reason; + mTimestamp = SystemClock.uptimeMillis(); + mWallTime = System.currentTimeMillis(); + mInFullscreenMode = inFullscreenMode; + mRequestWindowName = requestWindowName; + mImeControlTargetName = imeControlTargetName; + mImeTargetNameFromWm = imeTargetName; + mImeSurfaceParentName = imeSurfaceParentName; + } + } + + void addEntry(@NonNull Entry entry) { + final int index = mNextIndex; + mEntries[index] = entry; + mNextIndex = (mNextIndex + 1) % mEntries.length; + } + + void dump(@NonNull PrintWriter pw, @NonNull String prefix) { + final DateTimeFormatter formatter = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US) + .withZone(ZoneId.systemDefault()); + + for (int i = 0; i < mEntries.length; ++i) { + final Entry entry = mEntries[(i + mNextIndex) % mEntries.length]; + if (entry == null) { + continue; + } + pw.print(prefix); + pw.println("SoftInputShowHide #" + entry.mSequenceNumber + ":"); + + pw.print(prefix); + pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime)) + + " (timestamp=" + entry.mTimestamp + ")"); + + pw.print(prefix); + pw.print(" reason=" + InputMethodDebug.softInputDisplayReasonToString( + entry.mReason)); + pw.println(" inFullscreenMode=" + entry.mInFullscreenMode); + + pw.print(prefix); + pw.println(" requestClient=" + entry.mClientState); + + pw.print(prefix); + pw.println(" focusedWindowName=" + entry.mFocusedWindowName); + + pw.print(prefix); + pw.println(" requestWindowName=" + entry.mRequestWindowName); + + pw.print(prefix); + pw.println(" imeControlTargetName=" + entry.mImeControlTargetName); + + pw.print(prefix); + pw.println(" imeTargetNameFromWm=" + entry.mImeTargetNameFromWm); + + pw.print(prefix); + pw.println(" imeSurfaceParentName=" + entry.mImeSurfaceParentName); + + pw.print(prefix); + pw.print(" editorInfo:"); + if (entry.mEditorInfo != null) { + pw.print(" inputType=" + entry.mEditorInfo.inputType); + pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions); + pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId); + } else { + pw.println(" null"); + } + + pw.print(prefix); + pw.println(" focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString( + entry.mFocusedWindowSoftInputMode)); + } + } +} diff --git a/services/core/java/com/android/server/inputmethod/StartInputHistory.java b/services/core/java/com/android/server/inputmethod/StartInputHistory.java new file mode 100644 index 000000000000..3a39434f2d02 --- /dev/null +++ b/services/core/java/com/android/server/inputmethod/StartInputHistory.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.inputmethod; + +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.app.ActivityManager; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; + +import com.android.internal.inputmethod.InputMethodDebug; +import com.android.internal.inputmethod.StartInputReason; + +import java.io.PrintWriter; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Locale; + +/** + * A ring buffer to store the history of {@link StartInputInfo}. + */ +final class StartInputHistory { + /** + * Entry size for non low-RAM devices. + * + * <p>TODO: Consider to follow what other system services have been doing to manage + * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p> + */ + private static final int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 32; + + /** + * Entry size for low-RAM devices. + * + * <p>TODO: Consider to follow what other system services have been doing to manage + * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p> + */ + private static final int ENTRY_SIZE_FOR_LOW_RAM_DEVICE = 5; + + private static int getEntrySize() { + if (ActivityManager.isLowRamDeviceStatic()) { + return ENTRY_SIZE_FOR_LOW_RAM_DEVICE; + } else { + return ENTRY_SIZE_FOR_HIGH_RAM_DEVICE; + } + } + + /** + * Backing store for the ring buffer. + */ + private final Entry[] mEntries = new Entry[getEntrySize()]; + + /** + * An index of {@link #mEntries}, to which next + * {@link #addEntry(StartInputInfo)} should + * write. + */ + private int mNextIndex = 0; + + /** + * Recyclable entry to store the information in {@link StartInputInfo}. + */ + private static final class Entry { + int mSequenceNumber; + long mTimestamp; + long mWallTime; + @UserIdInt + int mImeUserId; + @NonNull + String mImeTokenString; + int mImeDisplayId; + @NonNull + String mImeId; + @StartInputReason + int mStartInputReason; + boolean mRestarting; + @UserIdInt + int mTargetUserId; + int mTargetDisplayId; + @NonNull + String mTargetWindowString; + @NonNull + EditorInfo mEditorInfo; + @WindowManager.LayoutParams.SoftInputModeFlags + int mTargetWindowSoftInputMode; + int mClientBindSequenceNumber; + + Entry(@NonNull StartInputInfo original) { + set(original); + } + + void set(@NonNull StartInputInfo original) { + mSequenceNumber = original.mSequenceNumber; + mTimestamp = original.mTimestamp; + mWallTime = original.mWallTime; + mImeUserId = original.mImeUserId; + // Intentionally convert to String so as not to keep a strong reference to a Binder + // object. + mImeTokenString = String.valueOf(original.mImeToken); + mImeDisplayId = original.mImeDisplayId; + mImeId = original.mImeId; + mStartInputReason = original.mStartInputReason; + mRestarting = original.mRestarting; + mTargetUserId = original.mTargetUserId; + mTargetDisplayId = original.mTargetDisplayId; + // Intentionally convert to String so as not to keep a strong reference to a Binder + // object. + mTargetWindowString = String.valueOf(original.mTargetWindow); + mEditorInfo = original.mEditorInfo; + mTargetWindowSoftInputMode = original.mTargetWindowSoftInputMode; + mClientBindSequenceNumber = original.mClientBindSequenceNumber; + } + } + + /** + * Add a new entry and discard the oldest entry as needed. + * + * @param info {@link StartInputInfo} to be added. + */ + void addEntry(@NonNull StartInputInfo info) { + final int index = mNextIndex; + if (mEntries[index] == null) { + mEntries[index] = new Entry(info); + } else { + mEntries[index].set(info); + } + mNextIndex = (mNextIndex + 1) % mEntries.length; + } + + void dump(@NonNull PrintWriter pw, @NonNull String prefix) { + final DateTimeFormatter formatter = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US) + .withZone(ZoneId.systemDefault()); + + for (int i = 0; i < mEntries.length; ++i) { + final Entry entry = mEntries[(i + mNextIndex) % mEntries.length]; + if (entry == null) { + continue; + } + pw.print(prefix); + pw.println("StartInput #" + entry.mSequenceNumber + ":"); + + pw.print(prefix); + pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime)) + + " (timestamp=" + entry.mTimestamp + ")" + + " reason=" + + InputMethodDebug.startInputReasonToString(entry.mStartInputReason) + + " restarting=" + entry.mRestarting); + + pw.print(prefix); + pw.print(" imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]"); + pw.print(" imeUserId=" + entry.mImeUserId); + pw.println(" imeDisplayId=" + entry.mImeDisplayId); + + pw.print(prefix); + pw.println(" targetWin=" + entry.mTargetWindowString + + " [" + entry.mEditorInfo.packageName + "]" + + " targetUserId=" + entry.mTargetUserId + + " targetDisplayId=" + entry.mTargetDisplayId + + " clientBindSeq=" + entry.mClientBindSequenceNumber); + + pw.print(prefix); + pw.println(" softInputMode=" + InputMethodDebug.softInputModeToString( + entry.mTargetWindowSoftInputMode)); + + pw.print(prefix); + pw.println(" inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType) + + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions) + + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId) + + " fieldName=" + entry.mEditorInfo.fieldName + + " actionId=" + entry.mEditorInfo.actionId + + " actionLabel=" + entry.mEditorInfo.actionLabel); + } + } +} diff --git a/services/core/java/com/android/server/inputmethod/StartInputInfo.java b/services/core/java/com/android/server/inputmethod/StartInputInfo.java new file mode 100644 index 000000000000..1cff737d4a00 --- /dev/null +++ b/services/core/java/com/android/server/inputmethod/StartInputInfo.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.inputmethod; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.inputmethodservice.InputMethodService; +import android.os.IBinder; +import android.os.SystemClock; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; + +import com.android.internal.inputmethod.IInputMethod; +import com.android.internal.inputmethod.StartInputReason; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Internal state snapshot when + * {@link IInputMethod#startInput(IInputMethod.StartInputParams)} is about to be called. + * + * <p>Calling that IPC endpoint basically means that + * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called + * back in the current IME process shortly, which will also affect what the current IME starts + * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this + * snapshot will be taken every time when {@link InputMethodManagerService} is initiating a new + * logical input session between the client application and the current IME.</p> + * + * <p>Be careful to not keep strong references to this object forever, which can prevent + * {@link StartInputInfo#mImeToken} and {@link StartInputInfo#mTargetWindow} from being GC-ed. + * </p> + */ +final class StartInputInfo { + private static final AtomicInteger sSequenceNumber = new AtomicInteger(0); + + final int mSequenceNumber; + final long mTimestamp; + final long mWallTime; + @UserIdInt + final int mImeUserId; + @NonNull + final IBinder mImeToken; + final int mImeDisplayId; + @NonNull + final String mImeId; + @StartInputReason + final int mStartInputReason; + final boolean mRestarting; + @UserIdInt + final int mTargetUserId; + final int mTargetDisplayId; + @Nullable + final IBinder mTargetWindow; + @NonNull + final EditorInfo mEditorInfo; + @WindowManager.LayoutParams.SoftInputModeFlags + final int mTargetWindowSoftInputMode; + final int mClientBindSequenceNumber; + + StartInputInfo(@UserIdInt int imeUserId, @NonNull IBinder imeToken, int imeDisplayId, + @NonNull String imeId, @StartInputReason int startInputReason, boolean restarting, + @UserIdInt int targetUserId, int targetDisplayId, @Nullable IBinder targetWindow, + @NonNull EditorInfo editorInfo, + @WindowManager.LayoutParams.SoftInputModeFlags int targetWindowSoftInputMode, + int clientBindSequenceNumber) { + mSequenceNumber = sSequenceNumber.getAndIncrement(); + mTimestamp = SystemClock.uptimeMillis(); + mWallTime = System.currentTimeMillis(); + mImeUserId = imeUserId; + mImeToken = imeToken; + mImeDisplayId = imeDisplayId; + mImeId = imeId; + mStartInputReason = startInputReason; + mRestarting = restarting; + mTargetUserId = targetUserId; + mTargetDisplayId = targetDisplayId; + mTargetWindow = targetWindow; + mEditorInfo = editorInfo; + mTargetWindowSoftInputMode = targetWindowSoftInputMode; + mClientBindSequenceNumber = clientBindSequenceNumber; + } +} diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index b98424cfade4..c38fbda4f5fd 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -8210,7 +8210,7 @@ public class NotificationManagerService extends SystemService { try { return mTelecomManager.isInManagedCall() || mTelecomManager.isInSelfManagedCall(pkg, - /* hasCrossUserAccess */ true); + UserHandle.ALL); } catch (IllegalStateException ise) { // Telecom is not ready (this is likely early boot), so there are no calls. return false; @@ -12054,10 +12054,17 @@ public class NotificationManagerService extends SystemService { @Override public void onServiceAdded(ManagedServiceInfo info) { if (lifetimeExtensionRefactor()) { - // We explicitly check the status bar permission for the uid in the info object. - // We can't use the calling uid here because it's probably always system server. - // Note that this will also be true for the shell. - info.isSystemUi = getContext().checkPermission( + // Generally, only System or System UI should have the permissions to call + // registerSystemService. + // isCallerSystemOrPhone tells us whether the caller is System. We negate this, + // to eliminate cases where the service was added by the system. This leaves + // services registered by system server. + // To identify system UI, we explicitly check the status bar permission for the + // uid in the info object. + // We can't use the calling uid here because it belongs to system server. + // Note that this will also return true for the shell, but we deem this + // acceptable, for the purposes of testing. + info.isSystemUi = !isCallerSystemOrPhone() && getContext().checkPermission( android.Manifest.permission.STATUS_BAR_SERVICE, -1, info.uid) == PERMISSION_GRANTED; } diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index c7ebb3c2667b..c6bb99eed7ee 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -2116,6 +2116,18 @@ public class LauncherAppsService extends SystemService { @RequiresPermission(READ_FRAME_BUFFER) @Override + public void saveViewCaptureData() { + int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER); + if (PERMISSION_GRANTED == status) { + forEachViewCaptureWindow(this::dumpViewCaptureDataToWmTrace); + } else { + Log.w(TAG, "caller lacks permissions to save view capture data"); + } + } + + + @RequiresPermission(READ_FRAME_BUFFER) + @Override public void registerDumpCallback(@NonNull IDumpCallback cb) { int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER); if (PERMISSION_GRANTED == status) { diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 5974ac8bd41b..266418fd5b4a 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -4315,6 +4315,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean allowDuringSetup) { if (allowDuringSetup || isUserSetupComplete()) { mContext.startActivityAsUser(intent, bundle, handle); + dismissKeyboardShortcutsMenu(); } else { Slog.i(TAG, "Not starting activity because user setup is in progress: " + intent); } @@ -4365,6 +4366,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (statusbar != null) { statusbar.showRecentApps(triggeredFromAltTab); } + dismissKeyboardShortcutsMenu(); } private void toggleKeyboardShortcutsMenu(int deviceId) { diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 3607dddc66d5..7a710dc51004 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -40,6 +40,7 @@ cc_library_static { "com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp", "com_android_server_ConsumerIrService.cpp", "com_android_server_companion_virtual_InputController.cpp", + "com_android_server_companion_virtual_VirtualDeviceImpl.cpp", "com_android_server_devicepolicy_CryptoTestHelper.cpp", "com_android_server_display_DisplayControl.cpp", "com_android_server_display_SmallAreaDetectionController.cpp", @@ -214,6 +215,7 @@ cc_defaults { static_libs: [ "android.hardware.broadcastradio@common-utils-1x-lib", "libaidlcommonsupport", + "libvirtualdevicebuildflags", ], product_variables: { diff --git a/services/core/jni/com_android_server_companion_virtual_VirtualDeviceImpl.cpp b/services/core/jni/com_android_server_companion_virtual_VirtualDeviceImpl.cpp new file mode 100644 index 000000000000..1e6a9dbcb58e --- /dev/null +++ b/services/core/jni/com_android_server_companion_virtual_VirtualDeviceImpl.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <android_companion_virtualdevice_build_flags.h> +#include <nativehelper/JNIHelp.h> + +#include <array> + +#include "jni.h" + +namespace android { +namespace { + +jboolean nativeVirtualCameraServiceBuildFlagEnabled(JNIEnv* env, jobject clazz) { + return ::android::companion::virtualdevice::flags::virtual_camera_service_build_flag(); +} + +const std::array<JNINativeMethod, 1> kMethods = { + {{"nativeVirtualCameraServiceBuildFlagEnabled", "()Z", + (void*)nativeVirtualCameraServiceBuildFlagEnabled}}, +}; + +} // namespace + +int register_android_server_companion_virtual_VirtualDeviceImpl(JNIEnv* env) { + return jniRegisterNativeMethods(env, "com/android/server/companion/virtual/VirtualDeviceImpl", + kMethods.data(), kMethods.size()); +} + +} // namespace android diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 0936888b24a0..6464081d615a 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -65,6 +65,7 @@ int register_android_server_GpuService(JNIEnv* env); int register_android_server_stats_pull_StatsPullAtomService(JNIEnv* env); int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env); int register_android_server_companion_virtual_InputController(JNIEnv* env); +int register_android_server_companion_virtual_VirtualDeviceImpl(JNIEnv* env); int register_android_server_app_GameManagerService(JNIEnv* env); int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env); int register_com_android_server_display_DisplayControl(JNIEnv* env); @@ -128,6 +129,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_stats_pull_StatsPullAtomService(env); register_android_server_sensor_SensorService(vm, env); register_android_server_companion_virtual_InputController(env); + register_android_server_companion_virtual_VirtualDeviceImpl(env); register_android_server_app_GameManagerService(env); register_com_android_server_wm_TaskFpsCallbackController(env); register_com_android_server_display_DisplayControl(env); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 2112dae0f311..73d830dcde33 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1134,7 +1134,7 @@ public final class SystemServer implements Dumpable { ServiceManager.addService(Context.PLATFORM_COMPAT_SERVICE, platformCompat); ServiceManager.addService(Context.PLATFORM_COMPAT_NATIVE_SERVICE, new PlatformCompatNative(platformCompat)); - AppCompatCallbacks.install(new long[0]); + AppCompatCallbacks.install(new long[0], new long[0]); t.traceEnd(); // FileIntegrityService responds to requests from apps and the system. It needs to run after diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java index a33e52f0dd75..e5d315358df6 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java @@ -91,8 +91,8 @@ public final class InputMethodManagerServiceTests { @Test public void testSoftInputShowHideHistoryDump_withNulls_doesntThrow() { var writer = new StringWriter(); - var history = new InputMethodManagerService.SoftInputShowHideHistory(); - history.addEntry(new InputMethodManagerService.SoftInputShowHideHistory.Entry( + var history = new SoftInputShowHideHistory(); + history.addEntry(new SoftInputShowHideHistory.Entry( null, null, null, diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index 64076e604414..3eced7fa025c 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -1631,12 +1631,25 @@ public class DisplayModeDirectorTest { director.start(sensorManager); director.injectSupportedModesByDisplay(supportedModesByDisplay); - setPeakRefreshRate(Float.POSITIVE_INFINITY); + // Disable Smooth Display + setPeakRefreshRate(RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE); Vote vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); Vote vote2 = director.getVote(DISPLAY_ID_2, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); + assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0, + /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE); + assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0, + /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE); + + // Enable Smooth Display + setPeakRefreshRate(Float.POSITIVE_INFINITY); + + vote1 = director.getVote(DISPLAY_ID, + Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); + vote2 = director.getVote(DISPLAY_ID_2, + Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0, /* frameRateHigh= */ 130); assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0, /* frameRateHigh= */ 140); } @@ -1654,10 +1667,18 @@ public class DisplayModeDirectorTest { SensorManager sensorManager = createMockSensorManager(lightSensor); director.start(sensorManager); - setPeakRefreshRate(peakRefreshRate); + // Disable Smooth Display + setPeakRefreshRate(RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE); Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, + /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE); + + // Enable Smooth Display + setPeakRefreshRate(peakRefreshRate); + + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); + assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ peakRefreshRate); } @@ -1759,11 +1780,23 @@ public class DisplayModeDirectorTest { director.start(sensorManager); director.injectSupportedModesByDisplay(supportedModesByDisplay); - setMinRefreshRate(Float.POSITIVE_INFINITY); + // Disable Force Peak Refresh Rate + setMinRefreshRate(0); Vote vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); Vote vote2 = director.getVote(DISPLAY_ID_2, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); + assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0, + /* frameRateHigh= */ Float.POSITIVE_INFINITY); + assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0, + /* frameRateHigh= */ Float.POSITIVE_INFINITY); + + // Enable Force Peak Refresh Rate + setMinRefreshRate(Float.POSITIVE_INFINITY); + + vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); + vote2 = director.getVote(DISPLAY_ID_2, + Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 130, /* frameRateHigh= */ Float.POSITIVE_INFINITY); assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 140, @@ -1783,9 +1816,17 @@ public class DisplayModeDirectorTest { SensorManager sensorManager = createMockSensorManager(lightSensor); director.start(sensorManager); - setMinRefreshRate(minRefreshRate); + // Disable Force Peak Refresh Rate + setMinRefreshRate(0); Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); + assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, + /* frameRateHigh= */ Float.POSITIVE_INFINITY); + + // Enable Force Peak Refresh Rate + setMinRefreshRate(minRefreshRate); + + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ minRefreshRate, /* frameRateHigh= */ Float.POSITIVE_INFINITY); } @@ -1829,6 +1870,58 @@ public class DisplayModeDirectorTest { } @Test + public void testPeakAndMinRefreshRate_FlagEnabled_DisplayWithOneMode() { + when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled()) + .thenReturn(true); + DisplayModeDirector director = + new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags); + director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); + + Display.Mode[] modes1 = new Display.Mode[] { + new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720, + /* refreshRate= */ 60), + new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720, + /* refreshRate= */ 130), + }; + Display.Mode[] modes2 = new Display.Mode[] { + new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720, + /* refreshRate= */ 60), + }; + SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>(); + supportedModesByDisplay.put(DISPLAY_ID, modes1); + supportedModesByDisplay.put(DISPLAY_ID_2, modes2); + + Sensor lightSensor = createLightSensor(); + SensorManager sensorManager = createMockSensorManager(lightSensor); + director.start(sensorManager); + director.injectSupportedModesByDisplay(supportedModesByDisplay); + + // Disable Force Peak Refresh Rate and Smooth Display + setMinRefreshRate(0); + setPeakRefreshRate(RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE); + + // Even though the highest refresh rate of the second display == the current min refresh + // rate == 60, Force Peak Refresh Rate should remain disabled + Vote vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); + Vote vote2 = director.getVote(DISPLAY_ID_2, + Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); + assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0, + /* frameRateHigh= */ Float.POSITIVE_INFINITY); + assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0, + /* frameRateHigh= */ Float.POSITIVE_INFINITY); + + // Even though the highest refresh rate of the second display == the current peak refresh + // rate == 60, Smooth Display should remain disabled + vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); + vote2 = director.getVote(DISPLAY_ID_2, + Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); + assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0, + /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE); + assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0, + /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE); + } + + @Test public void testSensorRegistration() { // First, configure brightness zones or DMD won't register for sensor data. final FakeDeviceConfig config = mInjector.getDeviceConfig(); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java index caa08647628e..a8b792e30485 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java @@ -213,7 +213,7 @@ public class AsyncProcessStartTest { any(), any(), any(), any(), any(), any(), any(), - any(), + any(), any(), anyLong(), anyLong()); final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid)); @@ -277,7 +277,7 @@ public class AsyncProcessStartTest { null, null, null, null, null, null, - null, null, + null, null, null, 0, 0); // Sleep until timeout should have triggered diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java index a2756ffc9add..0ba74c62b9fe 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -1413,6 +1413,9 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest { final BroadcastRecord userPresentRecord2 = makeBroadcastRecord(userPresent); mImpl.enqueueBroadcastLocked(userPresentRecord1); + // Wait for a few ms before sending another broadcast to allow comparing the + // enqueue timestamps of these broadcasts. + SystemClock.sleep(5); mImpl.enqueueBroadcastLocked(userPresentRecord2); final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN, diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java index fcf761fb6607..67be93b3b49f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java @@ -215,7 +215,7 @@ public class ProcessObserverTest { any(), any(), any(), any(), any(), any(), any(), - any(), + any(), any(), anyLong(), anyLong()); final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid)); r.setPid(myPid()); @@ -263,7 +263,7 @@ public class ProcessObserverTest { null, null, null, null, null, null, - null, null, + null, null, null, 0, 0); return app; } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java index e168596b8eb2..16d05b157727 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -214,7 +214,7 @@ public class AbstractAccessibilityServiceConnectionTest { .thenReturn(mA11yWindowInfos.get(0)); when(mMockA11yWindowManager.findA11yWindowInfoByIdLocked(PIP_WINDOWID)) .thenReturn(mA11yWindowInfos.get(1)); - when(mMockA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked(USER_ID, + when(mMockA11yWindowManager.getDisplayIdByUserIdAndWindowId(USER_ID, WINDOWID_ONSECONDDISPLAY)).thenReturn(SECONDARY_DISPLAY_ID); when(mMockA11yWindowManager.getWindowListLocked(SECONDARY_DISPLAY_ID)) .thenReturn(mA11yWindowInfosOnSecondDisplay); diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java index ef15f60101d4..36b163ec84b6 100644 --- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java @@ -173,6 +173,25 @@ public class CompatConfigTest { } @Test + public void testGetLoggableChanges() throws Exception { + final long disabledChangeId = 1234L; + final long enabledLatestChangeId = 2345L; + final long enabledOlderChangeId = 3456L; + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + // Disabled changes should not be logged. + .addDisabledChangeWithId(disabledChangeId) + // A change targeting the latest sdk should be logged. + .addEnableSinceSdkChangeWithId(3, enabledLatestChangeId) + // A change targeting an old sdk should not be logged. + .addEnableSinceSdkChangeWithId(1, enabledOlderChangeId) + .build(); + + assertThat(compatConfig.getLoggableChanges( + ApplicationInfoBuilder.create().withTargetSdk(3).build())) + .asList().containsExactly(enabledLatestChangeId); + } + + @Test public void testPackageOverrideEnabled() throws Exception { CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) .addDisabledChangeWithId(1234L) diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 03f27493c3c7..26cda65309ad 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -12008,7 +12008,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // style + self managed call - bypasses block when(mTelecomManager.isInSelfManagedCall( - r.getSbn().getPackageName(), true)).thenReturn(true); + r.getSbn().getPackageName(), UserHandle.ALL)).thenReturn(true); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue(); @@ -12091,7 +12091,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // style + self managed call - bypasses block mService.clearNotifications(); reset(mUsageStats); - when(mTelecomManager.isInSelfManagedCall(r.getSbn().getPackageName(), true)) + when(mTelecomManager.isInSelfManagedCall(r.getSbn().getPackageName(), UserHandle.ALL)) .thenReturn(true); mService.addEnqueuedNotification(r); diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index dc504cac2e12..a35a35acb6c1 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -153,7 +153,8 @@ public class UsageStatsService extends SystemService implements = SystemProperties.getBoolean("persist.debug.time_correction", true); private static final boolean USE_DEDICATED_HANDLER_THREAD = - SystemProperties.getBoolean("persist.debug.use_dedicated_handler_thread", false); + SystemProperties.getBoolean("persist.debug.use_dedicated_handler_thread", + Flags.useDedicatedHandlerThread()); static final boolean DEBUG = false; // Never submit with true static final boolean DEBUG_RESPONSE_STATS = DEBUG || Log.isLoggable(TAG, Log.DEBUG); diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 048b1b290dde..ff4be5586bc1 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -2797,7 +2797,9 @@ public class TelecomManager { /** * Determines whether there are any ongoing {@link PhoneAccount#CAPABILITY_SELF_MANAGED} - * calls for a given {@code packageName} and {@code userHandle}. + * calls for a given {@code packageName} and {@code userHandle}. If UserHandle.ALL or a user + * that isn't the calling user is passed in, the caller will need to have granted the ability + * to interact across users. * * @param packageName the package name of the app to check calls for. * @param userHandle the user handle to check calls for. @@ -2816,41 +2818,7 @@ public class TelecomManager { if (service != null) { try { return service.isInSelfManagedCall(packageName, userHandle, - mContext.getOpPackageName(), false); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException isInSelfManagedCall: " + e); - e.rethrowFromSystemServer(); - return false; - } - } else { - throw new IllegalStateException("Telecom service is not present"); - } - } - - /** - * Determines whether there are any ongoing {@link PhoneAccount#CAPABILITY_SELF_MANAGED} - * calls for a given {@code packageName} amongst all users, given that detectForAllUsers is true - * and the caller has the ability to interact across users. If detectForAllUsers isn't enabled, - * the calls will be checked against the caller. - * - * @param packageName the package name of the app to check calls for. - * @param detectForAllUsers indicates if calls should be detected across all users. - * @return {@code true} if there are ongoing calls, {@code false} otherwise. - * @throws SecurityException if detectForAllUsers is true and the caller does not grant the - * ability to interact across users. - * @hide - */ - @SystemApi - @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) - @RequiresPermission(allOf = {Manifest.permission.READ_PRIVILEGED_PHONE_STATE, - Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true) - public boolean isInSelfManagedCall(@NonNull String packageName, - boolean detectForAllUsers) { - ITelecomService service = getTelecomService(); - if (service != null) { - try { - return service.isInSelfManagedCall(packageName, null, - mContext.getOpPackageName(), detectForAllUsers); + mContext.getOpPackageName()); } catch (RemoteException e) { Log.e(TAG, "RemoteException isInSelfManagedCall: " + e); e.rethrowFromSystemServer(); diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index 302a472b77e4..112471b2af57 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -401,7 +401,7 @@ interface ITelecomService { * @see TelecomServiceImpl#isInSelfManagedCall */ boolean isInSelfManagedCall(String packageName, in UserHandle userHandle, - String callingPackage, boolean detectForAllUsers); + String callingPackage); /** * @see TelecomServiceImpl#addCall diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 88acbabc0e0f..a047b97d8592 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -18852,7 +18852,7 @@ public class TelephonyManager { */ @SystemApi @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) - @RequiresPermission(android.Manifest.permission.DUMP) + @RequiresPermission(android.Manifest.permission.READ_DROPBOX_DATA) public void persistEmergencyCallDiagnosticData(@NonNull String dropboxTag, @NonNull EmergencyCallDiagnosticData data) { try { diff --git a/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl b/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl index e69b60b3a37c..c3495996a231 100644 --- a/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl +++ b/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl @@ -26,5 +26,5 @@ oneway interface IQualifiedNetworksServiceCallback { void onQualifiedNetworkTypesChanged(int apnTypes, in int[] qualifiedNetworkTypes); void onNetworkValidationRequested(int networkCapability, IIntegerConsumer callback); - void onReconnectQualifedNetworkType(int apnTypes, int qualifiedNetworkType); + void onReconnectQualifiedNetworkType(int apnTypes, int qualifiedNetworkType); } diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java index 7bfe04d025c8..f775de6ebef4 100644 --- a/telephony/java/android/telephony/data/QualifiedNetworksService.java +++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java @@ -238,7 +238,7 @@ public abstract class QualifiedNetworksService extends Service { @AccessNetworkConstants.RadioAccessNetworkType int qualifiedNetworkType) { if (mCallback != null) { try { - mCallback.onReconnectQualifedNetworkType(apnTypes, qualifiedNetworkType); + mCallback.onReconnectQualifiedNetworkType(apnTypes, qualifiedNetworkType); } catch (RemoteException e) { loge("Failed to call onReconnectQualifiedNetworkType. " + e); } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt index 17f91ebad771..060015bcc4b2 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt @@ -40,9 +40,10 @@ abstract class BaseTest constructor( protected val flicker: LegacyFlickerTest, protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(), + protected val tapl: LauncherInstrumentation = LauncherInstrumentation() ) { - protected val tapl: LauncherInstrumentation by lazy { - LauncherInstrumentation().also { it.expectedRotationCheckEnabled = true } + init { + tapl.setExpectedRotationCheckEnabled(true) } private val logTag = this::class.java.simpleName diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java index 755636aef7ed..75284c712bd2 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -43,13 +43,13 @@ import android.os.SystemProperties; import android.os.test.TestLooper; import android.provider.DeviceConfig; import android.util.AtomicFile; -import android.util.LongArrayQueue; import android.util.Xml; +import android.utils.LongArrayQueue; +import android.utils.XmlUtils; import androidx.test.InstrumentationRegistry; import com.android.dx.mockito.inline.extended.ExtendedMockito; -import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.PackageWatchdog.HealthCheckState; |