diff options
196 files changed, 4199 insertions, 1690 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index ab20edc8ba6e..95b61559e839 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -39,6 +39,7 @@ aconfig_srcjars = [ ":android.multiuser.flags-aconfig-java{.generated_srcjars}", ":android.app.flags-aconfig-java{.generated_srcjars}", ":android.credentials.flags-aconfig-java{.generated_srcjars}", + ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}", ] filegroup { @@ -369,3 +370,16 @@ java_aconfig_library { aconfig_declarations: "android.credentials.flags-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// Content Protection +aconfig_declarations { + name: "android.view.contentprotection.flags-aconfig", + package: "android.view.contentprotection.flags", + srcs: ["core/java/android/view/contentprotection/flags/*.aconfig"], +} + +java_aconfig_library { + name: "android.view.contentprotection.flags-aconfig-java", + aconfig_declarations: "android.view.contentprotection.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/api/Android.bp b/api/Android.bp index f017a47a4013..45e70719e6c0 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -62,15 +62,6 @@ metalava_cmd = "$(location metalava)" metalava_cmd += " -J--add-opens=java.base/java.util=ALL-UNNAMED " metalava_cmd += " --quiet " -genrule { - name: "current-api-xml", - tools: ["metalava"], - srcs: [":frameworks-base-api-current.txt"], - out: ["current.api"], - cmd: metalava_cmd + "signature-to-jdiff $(in) $(out)", - visibility: ["//visibility:public"], -} - combined_apis { name: "frameworks-base-api", bootclasspath: [ diff --git a/api/OWNERS b/api/OWNERS index bf6216c168e8..965093c9ab38 100644 --- a/api/OWNERS +++ b/api/OWNERS @@ -3,7 +3,10 @@ hansson@google.com # Modularization team file:platform/packages/modules/common:/OWNERS +# Soong plugin owned by Soong team. +per-file *.go,go.mod,go.work,go.work.sum = file:platform/build/soong:/OWNERS + per-file Android.bp = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION} # For metalava team to disable lint checks in platform -per-file Android.bp = aurimas@google.com,emberrose@google.com,sjgilbert@google.com +per-file Android.bp = aurimas@google.com,emberrose@google.com diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index e5e0ad377b03..2d9c988556ec 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -359,13 +359,15 @@ java_library { ], srcs: [":module-lib-api-stubs-docs-non-updatable"], libs: [ + // We cannot depend on all-modules-module-lib-stubs, because the module-lib stubs + // depend on this stub. We resolve dependencies on APIs in modules by depending + // on a prebuilt of the whole platform (sdk_system_current_android). + // That prebuilt does not include module-lib APIs, so use the prebuilt module-lib + // stubs for modules that export module-lib stubs that the non-updatable part + // depends on. "sdk_module-lib_current_framework-tethering", "sdk_module-lib_current_framework-connectivity-t", - "sdk_public_current_framework-bluetooth", - // NOTE: The below can be removed once the prebuilt stub contains bluetooth. "sdk_system_current_android", - // NOTE: The below can be removed once the prebuilt stub contains IKE. - "sdk_system_current_android.net.ipsec.ike", ], dist: { dir: "apistubs/android/module-lib", diff --git a/core/api/current.txt b/core/api/current.txt index 43aaee8dd45e..2b50e382a34f 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -15224,6 +15224,7 @@ package android.graphics { method public int getMaxAnisotropy(); method public void setFilterMode(int); method public void setMaxAnisotropy(@IntRange(from=1) int); + method @FlaggedApi("com.android.graphics.hwui.flags.gainmap_animations") public void setOverrideGainmap(@Nullable android.graphics.Gainmap); field public static final int FILTER_MODE_DEFAULT = 0; // 0x0 field public static final int FILTER_MODE_LINEAR = 2; // 0x2 field public static final int FILTER_MODE_NEAREST = 1; // 0x1 diff --git a/core/api/system-current.txt b/core/api/system-current.txt index e99725968e55..358c8e72064b 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -12731,16 +12731,11 @@ package android.service.voice { public final class HotwordTrainingData implements android.os.Parcelable { method public int describeContents(); - method public static int getMaxTrainingDataSize(); + method public static int getMaxTrainingDataBytes(); method public int getTimeoutStage(); - method @NonNull public java.util.List<android.service.voice.HotwordTrainingAudio> getTrainingAudios(); + method @NonNull public java.util.List<android.service.voice.HotwordTrainingAudio> getTrainingAudioList(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.HotwordTrainingData> CREATOR; - field public static final int TIMEOUT_STAGE_EARLY = 2; // 0x2 - field public static final int TIMEOUT_STAGE_LATE = 4; // 0x4 - field public static final int TIMEOUT_STAGE_MIDDLE = 3; // 0x3 - field public static final int TIMEOUT_STAGE_UNKNOWN = 0; // 0x0 - field public static final int TIMEOUT_STAGE_VERY_EARLY = 1; // 0x1 } public static final class HotwordTrainingData.Builder { @@ -12748,7 +12743,7 @@ package android.service.voice { method @NonNull public android.service.voice.HotwordTrainingData.Builder addTrainingAudio(@NonNull android.service.voice.HotwordTrainingAudio); method @NonNull public android.service.voice.HotwordTrainingData build(); method @NonNull public android.service.voice.HotwordTrainingData.Builder setTimeoutStage(int); - method @NonNull public android.service.voice.HotwordTrainingData.Builder setTrainingAudios(@NonNull java.util.List<android.service.voice.HotwordTrainingAudio>); + method @NonNull public android.service.voice.HotwordTrainingData.Builder setTrainingAudioList(@NonNull java.util.List<android.service.voice.HotwordTrainingAudio>); } public interface SandboxedDetectionInitializer { diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt index b11a686d8f32..4fb7b6bddaac 100644 --- a/core/api/system-lint-baseline.txt +++ b/core/api/system-lint-baseline.txt @@ -19,8 +19,12 @@ ListenerLast: android.telephony.satellite.SatelliteManager#stopSatelliteTransmis Listeners should always be at end of argument list (method `stopSatelliteTransmissionUpdates`) ListenerLast: android.telephony.satellite.SatelliteManager#stopSatelliteTransmissionUpdates(android.telephony.satellite.SatelliteTransmissionUpdateCallback, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>) parameter #2: Listeners should always be at end of argument list (method `stopSatelliteTransmissionUpdates`) - - +MissingGetterMatchingBuilder: android.service.voice.HotwordTrainingData.Builder#addTrainingAudio(android.service.voice.HotwordTrainingAudio): + android.service.voice.HotwordTrainingData does not declare a `getTrainingAudios()` method matching method android.service.voice.HotwordTrainingData.Builder.addTrainingAudio(android.service.voice.HotwordTrainingAudio) +MissingGetterMatchingBuilder: android.telecom.CallScreeningService.CallResponse.Builder#setShouldScreenCallViaAudioProcessing(boolean): + android.telecom.CallScreeningService.CallResponse does not declare a `shouldScreenCallViaAudioProcessing()` method matching method android.telecom.CallScreeningService.CallResponse.Builder.setShouldScreenCallViaAudioProcessing(boolean) +MissingGetterMatchingBuilder: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String): + android.telephony.mbms.DownloadRequest does not declare a `getServiceId()` method matching method android.telephony.mbms.DownloadRequest.Builder.setServiceId(String) MissingNullability: android.media.soundtrigger.SoundTriggerDetectionService#onUnbind(android.content.Intent) parameter #0: Missing nullability on parameter `intent` in method `onUnbind` MissingNullability: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context) parameter #0: @@ -327,8 +331,12 @@ UnflaggedApi: android.service.voice.HotwordTrainingData#describeContents(): New API must be flagged with @FlaggedApi: method android.service.voice.HotwordTrainingData.describeContents() UnflaggedApi: android.service.voice.HotwordTrainingData#getMaxTrainingDataSize(): New API must be flagged with @FlaggedApi: method android.service.voice.HotwordTrainingData.getMaxTrainingDataSize() +UnflaggedApi: android.service.voice.HotwordTrainingData#getMaxTrainingDataBytes(): + New API must be flagged with @FlaggedApi: method android.service.voice.HotwordTrainingData.getMaxTrainingDataBytes() UnflaggedApi: android.service.voice.HotwordTrainingData#getTimeoutStage(): New API must be flagged with @FlaggedApi: method android.service.voice.HotwordTrainingData.getTimeoutStage() +UnflaggedApi: android.service.voice.HotwordTrainingData#getTrainingAudioList(): + New API must be flagged with @FlaggedApi: method android.service.voice.HotwordTrainingData.getTrainingAudioList() UnflaggedApi: android.service.voice.HotwordTrainingData#getTrainingAudios(): New API must be flagged with @FlaggedApi: method android.service.voice.HotwordTrainingData.getTrainingAudios() UnflaggedApi: android.service.voice.HotwordTrainingData#writeToParcel(android.os.Parcel, int): @@ -343,6 +351,8 @@ UnflaggedApi: android.service.voice.HotwordTrainingData.Builder#build(): New API must be flagged with @FlaggedApi: method android.service.voice.HotwordTrainingData.Builder.build() UnflaggedApi: android.service.voice.HotwordTrainingData.Builder#setTimeoutStage(int): New API must be flagged with @FlaggedApi: method android.service.voice.HotwordTrainingData.Builder.setTimeoutStage(int) +UnflaggedApi: android.service.voice.HotwordTrainingData.Builder#setTrainingAudioList(java.util.List<android.service.voice.HotwordTrainingAudio>): + New API must be flagged with @FlaggedApi: method android.service.voice.HotwordTrainingData.Builder.setTrainingAudioList(java.util.List<android.service.voice.HotwordTrainingAudio>) UnflaggedApi: android.service.voice.HotwordTrainingData.Builder#setTrainingAudios(java.util.List<android.service.voice.HotwordTrainingAudio>): New API must be flagged with @FlaggedApi: method android.service.voice.HotwordTrainingData.Builder.setTrainingAudios(java.util.List<android.service.voice.HotwordTrainingAudio>) UnflaggedApi: android.telecom.StreamingCall#EXTRA_CALL_ID: diff --git a/core/java/android/app/time/TEST_MAPPING b/core/java/android/app/time/TEST_MAPPING index 951905bcbac5..780904861f0c 100644 --- a/core/java/android/app/time/TEST_MAPPING +++ b/core/java/android/app/time/TEST_MAPPING @@ -8,5 +8,38 @@ } ] } + ], + // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. + "postsubmit": [ + { + "name": "FrameworksCoreTests", + "options": [ + { + "include-filter": "android.app.timedetector." + }, + { + "include-filter": "android.app.timezonedetector." + } + ] + }, + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.timezonedetector." + }, + { + "include-filter": "com.android.server.timedetector." + } + ] + }, + { + "name": "CtsTimeTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } ] } diff --git a/core/java/android/app/timedetector/TEST_MAPPING b/core/java/android/app/timedetector/TEST_MAPPING new file mode 100644 index 000000000000..53fd74bc3c3a --- /dev/null +++ b/core/java/android/app/timedetector/TEST_MAPPING @@ -0,0 +1,32 @@ +{ + // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. + "postsubmit": [ + { + "name": "FrameworksCoreTests", + "options": [ + { + "include-filter": "android.app.time." + }, + { + "include-filter": "android.app.timedetector." + } + ] + }, + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.timedetector." + } + ] + }, + { + "name": "CtsTimeTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } + ] +} diff --git a/core/java/android/app/timezonedetector/TEST_MAPPING b/core/java/android/app/timezonedetector/TEST_MAPPING index 46f2319d69ba..5e64c83937f6 100644 --- a/core/java/android/app/timezonedetector/TEST_MAPPING +++ b/core/java/android/app/timezonedetector/TEST_MAPPING @@ -8,5 +8,32 @@ } ] } + ], + // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. + "postsubmit": [ + { + "name": "FrameworksCoreTests", + "options": [ + { + "include-filter": "android.app.time." + } + ] + }, + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.timezonedetector." + } + ] + }, + { + "name": "CtsTimeTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } ] } diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index baed7f9b2a38..39800f73058b 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -637,15 +637,15 @@ public final class VirtualDeviceManager { /** * Specifies a component name to be exempt from the current activity launch policy. * - * <p>If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVIY} allows activity - * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT}, + * <p>If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} allows activity + * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT}), * then the specified component will be blocked from launching. - * If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity - * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}, then - * the specified component will be allowed to launch.</p> + * If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity launches + * by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}), then the + * specified component will be allowed to launch.</p> * - * <p>Note that changing the activity launch policy will not affect current set of exempt - * components and it needs to be updated separately.</p> + * <p>Note that changing the activity launch policy will clear current set of exempt + * components.</p> * * @see #removeActivityPolicyExemption * @see #setDevicePolicy @@ -660,15 +660,15 @@ public final class VirtualDeviceManager { /** * Makes the specified component name to adhere to the default activity launch policy. * - * <p>If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVIY} allows activity - * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT}, + * <p>If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} allows activity + * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT}), * then the specified component will be allowed to launch. - * If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity - * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}, then - * the specified component will be blocked from launching.</p> + * If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity launches + * by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}), then the + * specified component will be blocked from launching.</p> * - * <p>Note that changing the activity launch policy will not affect current set of exempt - * components and it needs to be updated separately.</p> + * <p>Note that changing the activity launch policy will clear current set of exempt + * components.</p> * * @see #addActivityPolicyExemption * @see #setDevicePolicy diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index a9eb672c4e4d..1307dfc2665e 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -484,7 +484,6 @@ public final class NfcAdapter { /** * A callback to be invoked when the system successfully delivers your {@link NdefMessage} * to another device. - * @see #setOnNdefPushCompleteCallback * @deprecated this feature is removed. File sharing can work using other technology like * Bluetooth. */ @@ -496,7 +495,6 @@ public final class NfcAdapter { * <p>This callback is usually made on a binder thread (not the UI thread). * * @param event {@link NfcEvent} with the {@link NfcEvent#nfcAdapter} field set - * @see #setNdefPushMessageCallback */ public void onNdefPushComplete(NfcEvent event); } @@ -504,11 +502,11 @@ public final class NfcAdapter { /** * A callback to be invoked when another NFC device capable of NDEF push (Android Beam) * is within range. - * <p>Implement this interface and pass it to {@link + * <p>Implement this interface and pass it to {@code * NfcAdapter#setNdefPushMessageCallback setNdefPushMessageCallback()} in order to create an * {@link NdefMessage} at the moment that another device is within range for NFC. Using this * callback allows you to create a message with data that might vary based on the - * content currently visible to the user. Alternatively, you can call {@link + * content currently visible to the user. Alternatively, you can call {@code * #setNdefPushMessage setNdefPushMessage()} if the {@link NdefMessage} always contains the * same data. * @deprecated this feature is removed. File sharing can work using other technology like diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java index 8afd6de235a0..fec67b97030d 100644 --- a/core/java/android/os/PowerManagerInternal.java +++ b/core/java/android/os/PowerManagerInternal.java @@ -315,6 +315,13 @@ public abstract class PowerManagerInternal { public static final int MODE_DISPLAY_INACTIVE = 9; /** + * Mode: It indicates that display is changing layout due to rotation or fold + * unfold behavior. + * Defined in hardware/interfaces/power/aidl/android/hardware/power/Mode.aidl + */ + public static final int MODE_DISPLAY_CHANGE = 17; + + /** * SetPowerMode() is called to enable/disable specific hint mode, which * may result in adjustment of power/performance parameters of the * cpufreq governor and other controls on device side. diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 1294f983b3b9..5626b948618f 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -12,4 +12,11 @@ flag { namespace: "permissions" description: "enable voice activation permission APIs" bug: "287264308" +} + +flag { + name: "role_controller_in_system_server" + namespace: "permissions" + description: "enable role controller in system server" + bug: "302562590" }
\ No newline at end of file diff --git a/core/java/android/service/controls/Control.java b/core/java/android/service/controls/Control.java index 3b757d6e3dd3..33978be9fb82 100644 --- a/core/java/android/service/controls/Control.java +++ b/core/java/android/service/controls/Control.java @@ -50,7 +50,7 @@ import java.lang.annotation.RetentionPolicy; * and zone. Some of these values are defined by the user and/or the {@link ControlsProviderService} * and will be used to display the control as well as group them for management. * <p> - * Each object will have an associated {@link DeviceTypes.DeviceType}. This will determine the icons and colors + * Each object will have an associated {@link DeviceTypes}. This will determine the icons and colors * used to display it. * <p> * An {@link Intent} linking to the provider Activity that expands on this {@link Control} and @@ -420,7 +420,7 @@ public final class Control implements Parcelable { * This fixes the values relating to state of the {@link Control} as required by * {@link ControlsProviderService#createPublisherForAllAvailable}: * <ul> - * <li> Status: {@link Status#STATUS_UNKNOWN} + * <li> Status: {@link #STATUS_UNKNOWN} * <li> Control template: {@link ControlTemplate#getNoTemplateObject} * <li> Status text: {@code ""} * <li> Auth Required: {@code true} @@ -620,7 +620,7 @@ public final class Control implements Parcelable { * <li> Device type: {@link DeviceTypes#TYPE_UNKNOWN} * <li> Title: {@code ""} * <li> Subtitle: {@code ""} - * <li> Status: {@link Status#STATUS_UNKNOWN} + * <li> Status: {@link #STATUS_UNKNOWN} * <li> Control template: {@link ControlTemplate#getNoTemplateObject} * <li> Status text: {@code ""} * <li> Auth Required: {@code true} diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java index fce87db2bbf0..0272bb9842d6 100644 --- a/core/java/android/service/controls/ControlsProviderService.java +++ b/core/java/android/service/controls/ControlsProviderService.java @@ -155,7 +155,7 @@ public abstract class ControlsProviderService extends Service { * The user has interacted with a Control. The action is dictated by the type of * {@link ControlAction} that was sent. A response can be sent via * {@link Consumer#accept}, with the Integer argument being one of the provided - * {@link ControlAction.ResponseResult}. The Integer should indicate whether the action + * {@link ControlAction} response results. The Integer should indicate whether the action * was received successfully, or if additional prompts should be presented to * the user. Any visual control updates should be sent via the Publisher. diff --git a/core/java/android/service/controls/actions/ControlAction.java b/core/java/android/service/controls/actions/ControlAction.java index 10f526d6565c..4e382222547d 100644 --- a/core/java/android/service/controls/actions/ControlAction.java +++ b/core/java/android/service/controls/actions/ControlAction.java @@ -154,7 +154,7 @@ public abstract class ControlAction { public static final @ResponseResult int RESPONSE_CHALLENGE_PASSPHRASE = 5; /** - * The {@link ActionType} associated with this class. + * The action type associated with this class. */ public abstract @ActionType int getActionType(); diff --git a/core/java/android/service/controls/templates/ControlTemplate.java b/core/java/android/service/controls/templates/ControlTemplate.java index 3902d6af69e7..0dd950d596f6 100644 --- a/core/java/android/service/controls/templates/ControlTemplate.java +++ b/core/java/android/service/controls/templates/ControlTemplate.java @@ -137,7 +137,7 @@ public abstract class ControlTemplate { } /** - * The {@link TemplateType} associated with this class. + * The template type associated with this class. */ public abstract @TemplateType int getTemplateType(); diff --git a/core/java/android/service/timezone/TEST_MAPPING b/core/java/android/service/timezone/TEST_MAPPING new file mode 100644 index 000000000000..b0ce1dba3509 --- /dev/null +++ b/core/java/android/service/timezone/TEST_MAPPING @@ -0,0 +1,16 @@ +{ + // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. + "postsubmit": [ + { + "name": "FrameworksCoreTests", + "options": [ + { + "include-filter": "android.service.timezone." + } + ] + }, + { + "name": "CtsLocationTimeZoneManagerHostTest" + } + ] +} diff --git a/core/java/android/service/voice/HotwordTrainingData.java b/core/java/android/service/voice/HotwordTrainingData.java index 9dca77ed7f51..31aeb9ca219e 100644 --- a/core/java/android/service/voice/HotwordTrainingData.java +++ b/core/java/android/service/voice/HotwordTrainingData.java @@ -16,7 +16,6 @@ package android.service.voice; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; @@ -34,7 +33,7 @@ import java.util.List; * Contains training data related to hotword detection service. * * <p>The constructed object's size must be within - * {@link HotwordTrainingData#getMaxTrainingDataSize()} or an + * {@link HotwordTrainingData#getMaxTrainingDataBytes()} or an * {@link IllegalArgumentException} will be thrown on construction. Size of the object is calculated * by converting object to a {@link Parcel} and using the {@link Parcel#dataSize()}. * @@ -49,63 +48,26 @@ import java.util.List; genToString = true) @SystemApi public final class HotwordTrainingData implements Parcelable { - /** Max size for hotword training data. */ - public static int getMaxTrainingDataSize() { + /** Max size for hotword training data in bytes. */ + public static int getMaxTrainingDataBytes() { return 1024 * 1024; // 1 MB; } /** The list containing hotword audio that is useful for training. */ @NonNull @DataClass.PluralOf("trainingAudio") - private final List<HotwordTrainingAudio> mTrainingAudios; + private final List<HotwordTrainingAudio> mTrainingAudioList; - private static List<HotwordTrainingAudio> defaultTrainingAudios() { + private static List<HotwordTrainingAudio> defaultTrainingAudioList() { return Collections.emptyList(); } - /** Timeout stage is unknown. */ - public static final int TIMEOUT_STAGE_UNKNOWN = 0; - - /** - * Timeout stage value that represents that the model timed out very early while detecting - * hotword. - */ - public static final int TIMEOUT_STAGE_VERY_EARLY = 1; - - /** - * Timeout stage value that represents that the model timed out early while detecting - * hotword. - */ - public static final int TIMEOUT_STAGE_EARLY = 2; - - /** - * Timeout stage value that represents that the model timed out in the middle while detecting - * hotword. - */ - public static final int TIMEOUT_STAGE_MIDDLE = 3; - - /** - * Timeout stage value that represents that the model timed out late while detecting - * hotword. - */ - public static final int TIMEOUT_STAGE_LATE = 4; - - /** @hide */ - @IntDef(prefix = {"TIMEOUT_STAGE"}, value = { - TIMEOUT_STAGE_UNKNOWN, - TIMEOUT_STAGE_VERY_EARLY, - TIMEOUT_STAGE_EARLY, - TIMEOUT_STAGE_MIDDLE, - TIMEOUT_STAGE_LATE, - }) - @interface HotwordTimeoutStage {} - - /** Stage when timeout occurred. */ - @HotwordTimeoutStage + /** App-defined stage when hotword model timed-out while running. + * <p> Returns 0 if unset. */ private final int mTimeoutStage; private static int defaultTimeoutStage() { - return TIMEOUT_STAGE_UNKNOWN; + return 0; } private void onConstructed() { @@ -115,10 +77,10 @@ public final class HotwordTrainingData implements Parcelable { int dataSizeBytes = parcel.dataSize(); parcel.recycle(); Preconditions.checkArgument( - dataSizeBytes < getMaxTrainingDataSize(), + dataSizeBytes < getMaxTrainingDataBytes(), TextUtils.formatSimple( - "Hotword training data of size %s exceeds size limit of %s!", - dataSizeBytes, getMaxTrainingDataSize())); + "Hotword training data of size %s exceeds size limit of %s bytes!", + dataSizeBytes, getMaxTrainingDataBytes())); } @@ -136,46 +98,14 @@ public final class HotwordTrainingData implements Parcelable { //@formatter:off - /** @hide */ - @IntDef(prefix = "TIMEOUT_STAGE_", value = { - TIMEOUT_STAGE_UNKNOWN, - TIMEOUT_STAGE_VERY_EARLY, - TIMEOUT_STAGE_EARLY, - TIMEOUT_STAGE_MIDDLE, - TIMEOUT_STAGE_LATE - }) - @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) - @DataClass.Generated.Member - public @interface TimeoutStage {} - - /** @hide */ - @DataClass.Generated.Member - public static String timeoutStageToString(@TimeoutStage int value) { - switch (value) { - case TIMEOUT_STAGE_UNKNOWN: - return "TIMEOUT_STAGE_UNKNOWN"; - case TIMEOUT_STAGE_VERY_EARLY: - return "TIMEOUT_STAGE_VERY_EARLY"; - case TIMEOUT_STAGE_EARLY: - return "TIMEOUT_STAGE_EARLY"; - case TIMEOUT_STAGE_MIDDLE: - return "TIMEOUT_STAGE_MIDDLE"; - case TIMEOUT_STAGE_LATE: - return "TIMEOUT_STAGE_LATE"; - default: return Integer.toHexString(value); - } - } - @DataClass.Generated.Member /* package-private */ HotwordTrainingData( - @NonNull List<HotwordTrainingAudio> trainingAudios, - @HotwordTimeoutStage int timeoutStage) { - this.mTrainingAudios = trainingAudios; + @NonNull List<HotwordTrainingAudio> trainingAudioList, + int timeoutStage) { + this.mTrainingAudioList = trainingAudioList; com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mTrainingAudios); + NonNull.class, null, mTrainingAudioList); this.mTimeoutStage = timeoutStage; - com.android.internal.util.AnnotationValidations.validate( - HotwordTimeoutStage.class, null, mTimeoutStage); onConstructed(); } @@ -184,15 +114,16 @@ public final class HotwordTrainingData implements Parcelable { * The list containing hotword audio that is useful for training. */ @DataClass.Generated.Member - public @NonNull List<HotwordTrainingAudio> getTrainingAudios() { - return mTrainingAudios; + public @NonNull List<HotwordTrainingAudio> getTrainingAudioList() { + return mTrainingAudioList; } /** - * Stage when timeout occurred. + * App-defined stage when hotword model timed-out while running. + * <p> Returns 0 if unset. */ @DataClass.Generated.Member - public @HotwordTimeoutStage int getTimeoutStage() { + public int getTimeoutStage() { return mTimeoutStage; } @@ -203,7 +134,7 @@ public final class HotwordTrainingData implements Parcelable { // String fieldNameToString() { ... } return "HotwordTrainingData { " + - "trainingAudios = " + mTrainingAudios + ", " + + "trainingAudioList = " + mTrainingAudioList + ", " + "timeoutStage = " + mTimeoutStage + " }"; } @@ -221,7 +152,7 @@ public final class HotwordTrainingData implements Parcelable { HotwordTrainingData that = (HotwordTrainingData) o; //noinspection PointlessBooleanExpression return true - && java.util.Objects.equals(mTrainingAudios, that.mTrainingAudios) + && java.util.Objects.equals(mTrainingAudioList, that.mTrainingAudioList) && mTimeoutStage == that.mTimeoutStage; } @@ -232,7 +163,7 @@ public final class HotwordTrainingData implements Parcelable { // int fieldNameHashCode() { ... } int _hash = 1; - _hash = 31 * _hash + java.util.Objects.hashCode(mTrainingAudios); + _hash = 31 * _hash + java.util.Objects.hashCode(mTrainingAudioList); _hash = 31 * _hash + mTimeoutStage; return _hash; } @@ -243,7 +174,7 @@ public final class HotwordTrainingData implements Parcelable { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } - dest.writeParcelableList(mTrainingAudios, flags); + dest.writeParcelableList(mTrainingAudioList, flags); dest.writeInt(mTimeoutStage); } @@ -258,16 +189,14 @@ public final class HotwordTrainingData implements Parcelable { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } - List<HotwordTrainingAudio> trainingAudios = new ArrayList<>(); - in.readParcelableList(trainingAudios, HotwordTrainingAudio.class.getClassLoader()); + List<HotwordTrainingAudio> trainingAudioList = new ArrayList<>(); + in.readParcelableList(trainingAudioList, HotwordTrainingAudio.class.getClassLoader()); int timeoutStage = in.readInt(); - this.mTrainingAudios = trainingAudios; + this.mTrainingAudioList = trainingAudioList; com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mTrainingAudios); + NonNull.class, null, mTrainingAudioList); this.mTimeoutStage = timeoutStage; - com.android.internal.util.AnnotationValidations.validate( - HotwordTimeoutStage.class, null, mTimeoutStage); onConstructed(); } @@ -293,8 +222,8 @@ public final class HotwordTrainingData implements Parcelable { @DataClass.Generated.Member public static final class Builder { - private @NonNull List<HotwordTrainingAudio> mTrainingAudios; - private @HotwordTimeoutStage int mTimeoutStage; + private @NonNull List<HotwordTrainingAudio> mTrainingAudioList; + private int mTimeoutStage; private long mBuilderFieldsSet = 0L; @@ -305,26 +234,27 @@ public final class HotwordTrainingData implements Parcelable { * The list containing hotword audio that is useful for training. */ @DataClass.Generated.Member - public @NonNull Builder setTrainingAudios(@NonNull List<HotwordTrainingAudio> value) { + public @NonNull Builder setTrainingAudioList(@NonNull List<HotwordTrainingAudio> value) { checkNotUsed(); mBuilderFieldsSet |= 0x1; - mTrainingAudios = value; + mTrainingAudioList = value; return this; } - /** @see #setTrainingAudios */ + /** @see #setTrainingAudioList */ @DataClass.Generated.Member public @NonNull Builder addTrainingAudio(@NonNull HotwordTrainingAudio value) { - if (mTrainingAudios == null) setTrainingAudios(new ArrayList<>()); - mTrainingAudios.add(value); + if (mTrainingAudioList == null) setTrainingAudioList(new ArrayList<>()); + mTrainingAudioList.add(value); return this; } /** - * Stage when timeout occurred. + * App-defined stage when hotword model timed-out while running. + * <p> Returns 0 if unset. */ @DataClass.Generated.Member - public @NonNull Builder setTimeoutStage(@HotwordTimeoutStage int value) { + public @NonNull Builder setTimeoutStage(int value) { checkNotUsed(); mBuilderFieldsSet |= 0x2; mTimeoutStage = value; @@ -337,13 +267,13 @@ public final class HotwordTrainingData implements Parcelable { mBuilderFieldsSet |= 0x4; // Mark builder used if ((mBuilderFieldsSet & 0x1) == 0) { - mTrainingAudios = defaultTrainingAudios(); + mTrainingAudioList = defaultTrainingAudioList(); } if ((mBuilderFieldsSet & 0x2) == 0) { mTimeoutStage = defaultTimeoutStage(); } HotwordTrainingData o = new HotwordTrainingData( - mTrainingAudios, + mTrainingAudioList, mTimeoutStage); return o; } @@ -357,10 +287,10 @@ public final class HotwordTrainingData implements Parcelable { } @DataClass.Generated( - time = 1693313864628L, + time = 1696092128091L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/service/voice/HotwordTrainingData.java", - inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"trainingAudio\") java.util.List<android.service.voice.HotwordTrainingAudio> mTrainingAudios\npublic static final int TIMEOUT_STAGE_UNKNOWN\npublic static final int TIMEOUT_STAGE_VERY_EARLY\npublic static final int TIMEOUT_STAGE_EARLY\npublic static final int TIMEOUT_STAGE_MIDDLE\npublic static final int TIMEOUT_STAGE_LATE\nprivate final @android.service.voice.HotwordTrainingData.HotwordTimeoutStage int mTimeoutStage\npublic static int getMaxTrainingDataSize()\nprivate static java.util.List<android.service.voice.HotwordTrainingAudio> defaultTrainingAudios()\nprivate static int defaultTimeoutStage()\nprivate void onConstructed()\nclass HotwordTrainingData extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)") + inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"trainingAudio\") java.util.List<android.service.voice.HotwordTrainingAudio> mTrainingAudioList\nprivate final int mTimeoutStage\npublic static int getMaxTrainingDataBytes()\nprivate static java.util.List<android.service.voice.HotwordTrainingAudio> defaultTrainingAudioList()\nprivate static int defaultTimeoutStage()\nprivate void onConstructed()\nclass HotwordTrainingData extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/service/watchdog/ExplicitHealthCheckService.java b/core/java/android/service/watchdog/ExplicitHealthCheckService.java index 49e00d6f6328..7befbfb0f370 100644 --- a/core/java/android/service/watchdog/ExplicitHealthCheckService.java +++ b/core/java/android/service/watchdog/ExplicitHealthCheckService.java @@ -151,7 +151,7 @@ public abstract class ExplicitHealthCheckService extends Service { */ @NonNull public abstract List<String> onGetRequestedPackages(); - private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true); + private final Handler mHandler = Handler.createAsync(Looper.getMainLooper()); @Nullable private RemoteCallback mCallback; @Override diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java index cb488b08456b..c5857347fd45 100644 --- a/core/java/android/text/FontConfig.java +++ b/core/java/android/text/FontConfig.java @@ -26,6 +26,7 @@ import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; +import android.graphics.fonts.FontFamily.Builder.VariableFontFamilyType; import android.graphics.fonts.FontStyle; import android.graphics.fonts.FontVariationAxis; import android.os.Build; @@ -528,6 +529,7 @@ public final class FontConfig implements Parcelable { private final @NonNull List<Font> mFonts; private final @NonNull LocaleList mLocaleList; private final @Variant int mVariant; + private final int mVariableFontFamilyType; /** @hide */ @Retention(SOURCE) @@ -567,10 +569,11 @@ public final class FontConfig implements Parcelable { * @hide Only system server can create this instance and passed via IPC. */ public FontFamily(@NonNull List<Font> fonts, @NonNull LocaleList localeList, - @Variant int variant) { + @Variant int variant, int variableFontFamilyType) { mFonts = fonts; mLocaleList = localeList; mVariant = variant; + mVariableFontFamilyType = variableFontFamilyType; } /** @@ -621,6 +624,20 @@ public final class FontConfig implements Parcelable { return mVariant; } + /** + * Returns the font family type. + * + * @see Builder#VARIABLE_FONT_FAMILY_TYPE_NONE + * @see Builder#VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL + * @see Builder#VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY + * @see Builder#VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT + * @hide + * @return variable font family type. + */ + public @VariableFontFamilyType int getVariableFontFamilyType() { + return mVariableFontFamilyType; + } + @Override public int describeContents() { return 0; @@ -631,6 +648,7 @@ public final class FontConfig implements Parcelable { dest.writeTypedList(mFonts, flags); dest.writeString8(mLocaleList.toLanguageTags()); dest.writeInt(mVariant); + dest.writeInt(mVariableFontFamilyType); } public static final @NonNull Creator<FontFamily> CREATOR = new Creator<FontFamily>() { @@ -641,8 +659,10 @@ public final class FontConfig implements Parcelable { source.readTypedList(fonts, Font.CREATOR); String langTags = source.readString8(); int variant = source.readInt(); + int varFamilyType = source.readInt(); - return new FontFamily(fonts, LocaleList.forLanguageTags(langTags), variant); + return new FontFamily(fonts, LocaleList.forLanguageTags(langTags), variant, + varFamilyType); } @Override diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index cdf5eec32fec..70e18963d921 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -2197,6 +2197,9 @@ public final class MotionEvent extends InputEvent implements Parcelable { float xOffset, float yOffset, float xPrecision, float yPrecision, long downTimeNanos, long eventTimeNanos, int pointerCount, PointerProperties[] pointerIds, PointerCoords[] pointerCoords) { + if (action == ACTION_CANCEL) { + flags |= FLAG_CANCELED; + } mNativePtr = nativeInitialize(mNativePtr, deviceId, source, displayId, action, flags, edgeFlags, metaState, buttonState, classification, xOffset, yOffset, xPrecision, yPrecision, downTimeNanos, eventTimeNanos, pointerCount, pointerIds, @@ -2387,6 +2390,11 @@ public final class MotionEvent extends InputEvent implements Parcelable { nativeSetFlags(mNativePtr, tainted ? flags | FLAG_TAINTED : flags & ~FLAG_TAINTED); } + private void setCanceled(boolean canceled) { + final int flags = getFlags(); + nativeSetFlags(mNativePtr, canceled ? flags | FLAG_CANCELED : flags & ~FLAG_CANCELED); + } + /** @hide */ public boolean isTargetAccessibilityFocus() { final int flags = getFlags(); @@ -3510,6 +3518,14 @@ public final class MotionEvent extends InputEvent implements Parcelable { * Sets this event's action. */ public final void setAction(int action) { + final int actionMasked = action & ACTION_MASK; + if (actionMasked == ACTION_CANCEL) { + setCanceled(true); + } else if (actionMasked == ACTION_POINTER_UP) { + // Do nothing - we don't know what the real intent here is + } else { + setCanceled(false); + } nativeSetAction(mNativePtr, action); } @@ -4157,6 +4173,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { /** @hide */ @Override public final void cancel() { + setCanceled(true); setAction(ACTION_CANCEL); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index f4213510a1c1..afa3157e35a0 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -3858,7 +3858,8 @@ public final class ViewRootImpl implements ViewParent, mPendingTransitions.clear(); } - handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction); + handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction, + "view not visible"); } else if (cancelAndRedraw) { mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener ? "predraw_" + mAttachInfo.mTreeObserver.getLastDispatchOnPreDrawCanceledReason() @@ -3873,7 +3874,8 @@ public final class ViewRootImpl implements ViewParent, mPendingTransitions.clear(); } if (!performDraw(mActiveSurfaceSyncGroup)) { - handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction); + handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction, + mLastPerformDrawSkippedReason); } } @@ -4665,6 +4667,10 @@ public final class ViewRootImpl implements ViewParent, return didProduceBuffer -> { if (!didProduceBuffer) { + Trace.instant(Trace.TRACE_TAG_VIEW, + "Transaction not synced due to no frame drawn-" + mTag); + Log.d(mTag, "Pending transaction will not be applied in sync with a draw " + + "because there was nothing new to draw"); mBlastBufferQueue.applyPendingTransactions(frame); } }; @@ -4687,8 +4693,7 @@ public final class ViewRootImpl implements ViewParent, return false; } - final boolean fullRedrawNeeded = - mFullRedrawNeeded || surfaceSyncGroup != null || mHasPendingTransactions; + final boolean fullRedrawNeeded = mFullRedrawNeeded || surfaceSyncGroup != null; mFullRedrawNeeded = false; mIsDrawing = true; @@ -4748,7 +4753,8 @@ public final class ViewRootImpl implements ViewParent, if (mSurfaceHolder != null && mSurface.isValid()) { usingAsyncReport = true; SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() -> { - handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction); + handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction, + "SurfaceHolder"); }); SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); @@ -4762,7 +4768,8 @@ public final class ViewRootImpl implements ViewParent, } if (!usingAsyncReport) { - handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction); + handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction, + "no async report"); } if (mPerformContentCapture) { @@ -4772,13 +4779,19 @@ public final class ViewRootImpl implements ViewParent, } private void handleSyncRequestWhenNoAsyncDraw(SurfaceSyncGroup surfaceSyncGroup, - @Nullable Transaction pendingTransaction) { + @Nullable Transaction pendingTransaction, String logReason) { if (surfaceSyncGroup != null) { if (pendingTransaction != null) { surfaceSyncGroup.addTransaction(pendingTransaction); } surfaceSyncGroup.markSyncReady(); } else if (pendingTransaction != null) { + Trace.instant(Trace.TRACE_TAG_VIEW, + "Transaction not synced due to " + logReason + "-" + mTag); + if (DEBUG_BLAST) { + Log.d(mTag, "Pending transaction will not be applied in sync with a draw due to " + + logReason); + } pendingTransaction.apply(); } } @@ -8993,7 +9006,8 @@ public final class ViewRootImpl implements ViewParent, mAdded = false; AnimationHandler.removeRequestor(this); } - handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction); + handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction, + "shutting down VRI"); WindowManagerGlobal.getInstance().doRemoveView(this); } @@ -11362,15 +11376,15 @@ public final class ViewRootImpl implements ViewParent, @Override public boolean applyTransactionOnDraw(@NonNull SurfaceControl.Transaction t) { if (mRemoved || !isHardwareEnabled()) { + Trace.instant(Trace.TRACE_TAG_VIEW, "applyTransactionOnDraw applyImmediately-" + mTag); + Log.d(mTag, "applyTransactionOnDraw: Applying transaction immediately"); t.apply(); } else { + Trace.instant(Trace.TRACE_TAG_VIEW, "applyTransactionOnDraw-" + mTag); // Copy and clear the passed in transaction for thread safety. The new transaction is // accessed on the render thread. mPendingTransaction.merge(t); mHasPendingTransactions = true; - // Schedule the traversal to ensure there's an attempt to draw a frame and apply the - // pending transactions. This is also where the registerFrameCallback will be scheduled. - scheduleTraversals(); } return true; } diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java index f543cab322bb..334c2b779f1a 100644 --- a/core/java/android/view/autofill/AutofillFeatureFlags.java +++ b/core/java/android/view/autofill/AutofillFeatureFlags.java @@ -255,6 +255,16 @@ public class AutofillFeatureFlags { // END AUTOFILL PCC CLASSIFICATION FLAGS + /** + * Define the max input length for autofill to show suggesiton UI + * + * E.g. if flag is set to 3, autofill will only show suggestions when user inputs less than 3 + * characters + * + * @hide + */ + public static final String DEVICE_CONFIG_MAX_INPUT_LENGTH_FOR_AUTOFILL = + "max_input_length_for_autofill"; /** * Sets a value of delay time to show up the inline tooltip view. @@ -295,6 +305,10 @@ public class AutofillFeatureFlags { DEFAULT_AFAA_SHOULD_INCLUDE_ALL_AUTOFILL_TYPE_NOT_NONE_VIEWS_IN_ASSIST_STRUCTURE = true; // END AUTOFILL FOR ALL APPS DEFAULTS + /** + * @hide + */ + public static final int DEFAULT_MAX_INPUT_LENGTH_FOR_AUTOFILL = 3; private AutofillFeatureFlags() {}; /** diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 6cf185a5b460..89fa83e5c7aa 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -3443,7 +3443,7 @@ public final class AutofillManager { return false; } for (String hint : hints) { - if (hint.equals(View.AUTOFILL_HINT_CREDENTIAL_MANAGER)) { + if (Objects.equals(hint, View.AUTOFILL_HINT_CREDENTIAL_MANAGER)) { return true; } } diff --git a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig new file mode 100644 index 000000000000..7e06f87d931a --- /dev/null +++ b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig @@ -0,0 +1,8 @@ +package: "android.view.contentprotection.flags" + +flag { + name: "blocklist_update_enabled" + namespace: "content_protection" + description: "If true, content protection blocklist is mutable and can be updated." + bug: "301658008" +} diff --git a/core/java/android/view/textclassifier/TextClassifierEvent.java b/core/java/android/view/textclassifier/TextClassifierEvent.java index 195565c5bc09..33db6715c327 100644 --- a/core/java/android/view/textclassifier/TextClassifierEvent.java +++ b/core/java/android/view/textclassifier/TextClassifierEvent.java @@ -551,8 +551,8 @@ public abstract class TextClassifierEvent implements Parcelable { * Sets the entity types. e.g. {@link TextClassifier#TYPE_ADDRESS}. * <p> * Supported types: - * <p>See {@link TextClassifier.EntityType} - * <p>See {@link ConversationAction.ActionType} + * <p>See {@link TextClassifier} types + * <p>See {@link ConversationAction} types * <p>See {@link ULocale#toLanguageTag()} */ @NonNull diff --git a/core/java/android/view/translation/TranslationCapability.java b/core/java/android/view/translation/TranslationCapability.java index b7e13dda9ff6..52760f728205 100644 --- a/core/java/android/view/translation/TranslationCapability.java +++ b/core/java/android/view/translation/TranslationCapability.java @@ -207,7 +207,7 @@ public final class TranslationCapability implements Parcelable { /** * Translation flags for settings that are supported by the - * {@link android.service.translation.TranslationService} between the {@link TranslationSpec}s + * translation service between the {@link TranslationSpec}s * provided in this capability. */ @DataClass.Generated.Member diff --git a/core/java/android/view/translation/TranslationManager.java b/core/java/android/view/translation/TranslationManager.java index 5aad823c374e..99544e80ed9d 100644 --- a/core/java/android/view/translation/TranslationManager.java +++ b/core/java/android/view/translation/TranslationManager.java @@ -56,7 +56,7 @@ import java.util.function.Consumer; * translation framework. * * <p>The TranslationManager manages {@link Translator}s and help bridge client calls to - * the server {@link android.service.translation.TranslationService} </p> + * the server translation service </p> */ @SystemService(Context.TRANSLATION_MANAGER_SERVICE) public final class TranslationManager { diff --git a/core/java/android/view/translation/TranslationRequest.java b/core/java/android/view/translation/TranslationRequest.java index 027edc21389f..ff11ffad28f5 100644 --- a/core/java/android/view/translation/TranslationRequest.java +++ b/core/java/android/view/translation/TranslationRequest.java @@ -27,7 +27,7 @@ import java.util.Collections; import java.util.List; /** - * Translation request sent to the {@link android.service.translation.TranslationService} by the + * Translation request sent to the translation service by the * {@link android.view.translation.Translator} which contains the text to be translated. */ @DataClass(genToString = true, genHiddenConstDefs = true, genBuilder = true) diff --git a/core/java/android/view/translation/TranslationResponse.java b/core/java/android/view/translation/TranslationResponse.java index b77f2e282650..3362fc007ded 100644 --- a/core/java/android/view/translation/TranslationResponse.java +++ b/core/java/android/view/translation/TranslationResponse.java @@ -20,7 +20,6 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; -import android.service.translation.TranslationService; import android.util.SparseArray; import com.android.internal.util.DataClass; @@ -30,17 +29,17 @@ import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** - * Response from the {@link TranslationService}, which contains the translated result. + * Response from the translation service, which contains the translated result. */ @DataClass(genBuilder = true, genToString = true, genHiddenConstDefs = true) public final class TranslationResponse implements Parcelable { /** - * The {@link TranslationService} was successful in translating. + * The translation service was successful in translating. */ public static final int TRANSLATION_STATUS_SUCCESS = 0; /** - * The {@link TranslationService} returned unknown translation result. + * The translation service returned unknown translation result. */ public static final int TRANSLATION_STATUS_UNKNOWN_ERROR = 1; /** diff --git a/core/java/android/view/translation/TranslationResponseValue.java b/core/java/android/view/translation/TranslationResponseValue.java index 9dff2d56322b..18a240daffa8 100644 --- a/core/java/android/view/translation/TranslationResponseValue.java +++ b/core/java/android/view/translation/TranslationResponseValue.java @@ -27,7 +27,7 @@ import com.android.internal.util.DataClass; import java.util.Objects; /** - * A translated response value from {@link android.service.translation.TranslationService}. + * A translated response value from translation service. */ @DataClass(genBuilder = true, genToString = true, genEqualsHashCode = true, genHiddenConstDefs = true) diff --git a/core/java/android/view/translation/ViewTranslationRequest.java b/core/java/android/view/translation/ViewTranslationRequest.java index a41749a2bc50..54b8ac2102c6 100644 --- a/core/java/android/view/translation/ViewTranslationRequest.java +++ b/core/java/android/view/translation/ViewTranslationRequest.java @@ -33,7 +33,7 @@ import java.util.Set; /** * Wrapper class representing a translation request associated with a {@link android.view.View} to - * be used by {@link android.service.translation.TranslationService}. + * be used by translation service. */ @DataClass(genBuilder = false, genToString = true, genEqualsHashCode = true, genGetters = false, genHiddenConstructor = true, genHiddenConstDefs = true) diff --git a/core/java/android/view/translation/ViewTranslationResponse.java b/core/java/android/view/translation/ViewTranslationResponse.java index d993114dba0a..134ff5a6b2c3 100644 --- a/core/java/android/view/translation/ViewTranslationResponse.java +++ b/core/java/android/view/translation/ViewTranslationResponse.java @@ -33,7 +33,7 @@ import java.util.Set; /** * Wrapper class representing a translation response associated with a {@link android.view.View} to - * be used by {@link android.service.translation.TranslationService}. + * be used by translation service. */ @DataClass(genBuilder = true, genToString = true, genEqualsHashCode = true, genGetters = false) public final class ViewTranslationResponse implements Parcelable { diff --git a/core/tests/coretests/src/android/app/time/TEST_MAPPING b/core/tests/coretests/src/android/app/time/TEST_MAPPING new file mode 100644 index 000000000000..9d711a271642 --- /dev/null +++ b/core/tests/coretests/src/android/app/time/TEST_MAPPING @@ -0,0 +1,13 @@ +{ + // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. + "postsubmit": [ + { + "name": "FrameworksCoreTests", + "options": [ + { + "include-filter": "android.app.time." + } + ] + } + ] +} diff --git a/core/tests/coretests/src/android/app/timedetector/TEST_MAPPING b/core/tests/coretests/src/android/app/timedetector/TEST_MAPPING new file mode 100644 index 000000000000..6c4d48de30a3 --- /dev/null +++ b/core/tests/coretests/src/android/app/timedetector/TEST_MAPPING @@ -0,0 +1,13 @@ +{ + // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. + "postsubmit": [ + { + "name": "FrameworksCoreTests", + "options": [ + { + "include-filter": "android.app.timedetector." + } + ] + } + ] +} diff --git a/core/tests/coretests/src/android/app/timezonedetector/TEST_MAPPING b/core/tests/coretests/src/android/app/timezonedetector/TEST_MAPPING new file mode 100644 index 000000000000..8872f642f604 --- /dev/null +++ b/core/tests/coretests/src/android/app/timezonedetector/TEST_MAPPING @@ -0,0 +1,13 @@ +{ + // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. + "postsubmit": [ + { + "name": "FrameworksCoreTests", + "options": [ + { + "include-filter": "android.app.timezonedetector." + } + ] + } + ] +} diff --git a/core/tests/coretests/src/android/graphics/FontListParserTest.java b/core/tests/coretests/src/android/graphics/FontListParserTest.java index d46f7625b596..4dd5889fdf5d 100644 --- a/core/tests/coretests/src/android/graphics/FontListParserTest.java +++ b/core/tests/coretests/src/android/graphics/FontListParserTest.java @@ -28,6 +28,7 @@ import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.fail; import android.graphics.fonts.FontCustomizationParser; +import android.graphics.fonts.FontFamily; import android.graphics.fonts.FontStyle; import android.os.LocaleList; import android.text.FontConfig; @@ -64,7 +65,8 @@ public final class FontListParserTest { Collections.singletonList(new FontConfig.FontFamily( Arrays.asList(new FontConfig.Font(new File("test.ttf"), null, "test", new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null)), - LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif"); + LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT, + FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)), "sans-serif"); FontConfig.NamedFamilyList family = readNamedFamily(xml); assertThat(family).isEqualTo(expected); } @@ -84,7 +86,8 @@ public final class FontListParserTest { new FontConfig.Font(new File("test.ttf"), null, "test", new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", "serif")), - LocaleList.forLanguageTags("en"), VARIANT_DEFAULT); + LocaleList.forLanguageTags("en"), VARIANT_DEFAULT, + FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE); FontConfig.FontFamily family = readFamily(xml); assertThat(family).isEqualTo(expected); @@ -101,7 +104,8 @@ public final class FontListParserTest { new FontConfig.Font(new File("test.ttf"), null, "test", new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null)), - LocaleList.forLanguageTags("en"), VARIANT_COMPACT); + LocaleList.forLanguageTags("en"), VARIANT_COMPACT, + FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE); FontConfig.FontFamily family = readFamily(xml); assertThat(family).isEqualTo(expected); @@ -118,7 +122,8 @@ public final class FontListParserTest { new FontConfig.Font(new File("test.ttf"), null, "test", new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null)), - LocaleList.forLanguageTags("en"), VARIANT_ELEGANT); + LocaleList.forLanguageTags("en"), VARIANT_ELEGANT, + FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE); FontConfig.FontFamily family = readFamily(xml); assertThat(family).isEqualTo(expected); @@ -140,7 +145,8 @@ public final class FontListParserTest { new FontStyle(100, FONT_SLANT_UPRIGHT), 0, "", null), new FontConfig.Font(new File("italic.ttf"), null, "test", new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC), 0, "", null)), - LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif"); + LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT, + FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)), "sans-serif"); FontConfig.NamedFamilyList family = readNamedFamily(xml); assertThat(family).isEqualTo(expected); } @@ -166,7 +172,8 @@ public final class FontListParserTest { new FontConfig.Font(new File("test-VF.ttf"), null, "test", new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "'wdth' 400.0,'wght' 700.0", null)), - LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), + LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT, + FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)), "sans-serif"); FontConfig.NamedFamilyList family = readNamedFamily(xml); assertThat(family).isEqualTo(expected); @@ -187,7 +194,8 @@ public final class FontListParserTest { new FontConfig.Font(new File("test.ttc"), null, "test", new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 1, "", null)), - LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), + LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT, + FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)), "sans-serif"); FontConfig.NamedFamilyList family = readNamedFamily(xml); assertThat(family).isEqualTo(expected); @@ -206,7 +214,8 @@ public final class FontListParserTest { new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null), new FontConfig.Font(new File("test.ttc"), null, "test", new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 1, "", null)), - LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif"); + LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT, + FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)), "sans-serif"); FontConfig.NamedFamilyList family = readNamedFamily(xml); assertThat(family).isEqualTo(expected); } @@ -372,6 +381,20 @@ public final class FontListParserTest { .isEqualTo("emoji.ttf"); } + @Test + public void varFamilyType() throws Exception { + String xml = "<?xml version='1.0' encoding='UTF-8'?>" + + "<familyset>" + + " <family name='sans-serif' varFamilyType='1'>" + + " <font>test.ttf</font>" + + " </family>" + + "</familyset>"; + FontConfig config = readFamilies(xml, true /* include non-existing font files */); + List<FontConfig.FontFamily> families = config.getFontFamilies(); + assertThat(families.size()).isEqualTo(1); // legacy one should be ignored. + assertThat(families.get(0).getVariableFontFamilyType()).isEqualTo(1); + } + private FontConfig readFamilies(String xml, boolean allowNonExisting) throws IOException, XmlPullParserException { ByteArrayInputStream buffer = new ByteArrayInputStream( diff --git a/core/tests/coretests/src/android/service/timezone/TEST_MAPPING b/core/tests/coretests/src/android/service/timezone/TEST_MAPPING new file mode 100644 index 000000000000..46f476f43861 --- /dev/null +++ b/core/tests/coretests/src/android/service/timezone/TEST_MAPPING @@ -0,0 +1,13 @@ +{ + // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. + "postsubmit": [ + { + "name": "FrameworksCoreTests", + "options": [ + { + "include-filter": "android.service.timezone." + } + ] + } + ] +} diff --git a/data/fonts/font_fallback.xml b/data/fonts/font_fallback.xml index 1e97fcee250d..02e032b86443 100644 --- a/data/fonts/font_fallback.xml +++ b/data/fonts/font_fallback.xml @@ -15,96 +15,9 @@ --> <familyset version="23"> <!-- first font is default --> - <family name="sans-serif"> - <font weight="100" style="normal">Roboto-Regular.ttf - <axis tag="ital" stylevalue="0" /> + <family name="sans-serif" varFamilyType="2"> + <font>Roboto-Regular.ttf <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="100" /> - </font> - <font weight="200" style="normal">Roboto-Regular.ttf - <axis tag="ital" stylevalue="0" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="200" /> - </font> - <font weight="300" style="normal">Roboto-Regular.ttf - <axis tag="ital" stylevalue="0" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="300" /> - </font> - <font weight="400" style="normal">Roboto-Regular.ttf - <axis tag="ital" stylevalue="0" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="400" /> - </font> - <font weight="500" style="normal">Roboto-Regular.ttf - <axis tag="ital" stylevalue="0" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="500" /> - </font> - <font weight="600" style="normal">Roboto-Regular.ttf - <axis tag="ital" stylevalue="0" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="600" /> - </font> - <font weight="700" style="normal">Roboto-Regular.ttf - <axis tag="ital" stylevalue="0" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="700" /> - </font> - <font weight="800" style="normal">Roboto-Regular.ttf - <axis tag="ital" stylevalue="0" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="800" /> - </font> - <font weight="900" style="normal">Roboto-Regular.ttf - <axis tag="ital" stylevalue="0" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="900" /> - </font> - <font weight="100" style="italic">Roboto-Regular.ttf - <axis tag="ital" stylevalue="1" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="100" /> - </font> - <font weight="200" style="italic">Roboto-Regular.ttf - <axis tag="ital" stylevalue="1" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="200" /> - </font> - <font weight="300" style="italic">Roboto-Regular.ttf - <axis tag="ital" stylevalue="1" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="300" /> - </font> - <font weight="400" style="italic">Roboto-Regular.ttf - <axis tag="ital" stylevalue="1" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="400" /> - </font> - <font weight="500" style="italic">Roboto-Regular.ttf - <axis tag="ital" stylevalue="1" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="500" /> - </font> - <font weight="600" style="italic">Roboto-Regular.ttf - <axis tag="ital" stylevalue="1" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="600" /> - </font> - <font weight="700" style="italic">Roboto-Regular.ttf - <axis tag="ital" stylevalue="1" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="700" /> - </font> - <font weight="800" style="italic">Roboto-Regular.ttf - <axis tag="ital" stylevalue="1" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="800" /> - </font> - <font weight="900" style="italic">Roboto-Regular.ttf - <axis tag="ital" stylevalue="1" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="900" /> </font> </family> @@ -119,96 +32,9 @@ <alias name="tahoma" to="sans-serif" /> <alias name="verdana" to="sans-serif" /> - <family name="sans-serif-condensed"> - <font weight="100" style="normal">Roboto-Regular.ttf - <axis tag="ital" stylevalue="0" /> - <axis tag="wdth" stylevalue="75" /> - <axis tag="wght" stylevalue="100" /> - </font> - <font weight="200" style="normal">Roboto-Regular.ttf - <axis tag="ital" stylevalue="0" /> - <axis tag="wdth" stylevalue="75" /> - <axis tag="wght" stylevalue="200" /> - </font> - <font weight="300" style="normal">Roboto-Regular.ttf - <axis tag="ital" stylevalue="0" /> - <axis tag="wdth" stylevalue="75" /> - <axis tag="wght" stylevalue="300" /> - </font> - <font weight="400" style="normal">Roboto-Regular.ttf - <axis tag="ital" stylevalue="0" /> - <axis tag="wdth" stylevalue="75" /> - <axis tag="wght" stylevalue="400" /> - </font> - <font weight="500" style="normal">Roboto-Regular.ttf - <axis tag="ital" stylevalue="0" /> - <axis tag="wdth" stylevalue="75" /> - <axis tag="wght" stylevalue="500" /> - </font> - <font weight="600" style="normal">Roboto-Regular.ttf - <axis tag="ital" stylevalue="0" /> - <axis tag="wdth" stylevalue="75" /> - <axis tag="wght" stylevalue="600" /> - </font> - <font weight="700" style="normal">Roboto-Regular.ttf - <axis tag="ital" stylevalue="0" /> - <axis tag="wdth" stylevalue="75" /> - <axis tag="wght" stylevalue="700" /> - </font> - <font weight="800" style="normal">Roboto-Regular.ttf - <axis tag="ital" stylevalue="0" /> - <axis tag="wdth" stylevalue="75" /> - <axis tag="wght" stylevalue="800" /> - </font> - <font weight="900" style="normal">Roboto-Regular.ttf - <axis tag="ital" stylevalue="0" /> - <axis tag="wdth" stylevalue="75" /> - <axis tag="wght" stylevalue="900" /> - </font> - <font weight="100" style="italic">Roboto-Regular.ttf - <axis tag="ital" stylevalue="1" /> - <axis tag="wdth" stylevalue="75" /> - <axis tag="wght" stylevalue="100" /> - </font> - <font weight="200" style="italic">Roboto-Regular.ttf - <axis tag="ital" stylevalue="1" /> - <axis tag="wdth" stylevalue="75" /> - <axis tag="wght" stylevalue="200" /> - </font> - <font weight="300" style="italic">Roboto-Regular.ttf - <axis tag="ital" stylevalue="1" /> - <axis tag="wdth" stylevalue="75" /> - <axis tag="wght" stylevalue="300" /> - </font> - <font weight="400" style="italic">Roboto-Regular.ttf - <axis tag="ital" stylevalue="1" /> - <axis tag="wdth" stylevalue="75" /> - <axis tag="wght" stylevalue="400" /> - </font> - <font weight="500" style="italic">Roboto-Regular.ttf - <axis tag="ital" stylevalue="1" /> - <axis tag="wdth" stylevalue="75" /> - <axis tag="wght" stylevalue="500" /> - </font> - <font weight="600" style="italic">Roboto-Regular.ttf - <axis tag="ital" stylevalue="1" /> - <axis tag="wdth" stylevalue="75" /> - <axis tag="wght" stylevalue="600" /> - </font> - <font weight="700" style="italic">Roboto-Regular.ttf - <axis tag="ital" stylevalue="1" /> - <axis tag="wdth" stylevalue="75" /> - <axis tag="wght" stylevalue="700" /> - </font> - <font weight="800" style="italic">Roboto-Regular.ttf - <axis tag="ital" stylevalue="1" /> - <axis tag="wdth" stylevalue="75" /> - <axis tag="wght" stylevalue="800" /> - </font> - <font weight="900" style="italic">Roboto-Regular.ttf - <axis tag="ital" stylevalue="1" /> + <family name="sans-serif-condensed" varFamilyType="2"> + <font>Roboto-Regular.ttf <axis tag="wdth" stylevalue="75" /> - <axis tag="wght" stylevalue="900" /> </font> </family> <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" /> @@ -246,13 +72,8 @@ <font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font> </family> - <family name="cursive"> - <font weight="400" style="normal">DancingScript-Regular.ttf - <axis tag="wght" stylevalue="400" /> - </font> - <font weight="700" style="normal">DancingScript-Regular.ttf - <axis tag="wght" stylevalue="700" /> - </font> + <family name="cursive" varFamilyType="1"> + <font>DancingScript-Regular.ttf</font> </family> <family name="sans-serif-smallcaps"> @@ -269,96 +90,9 @@ </family> <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/> - <family name="roboto-flex"> - <font weight="100" style="normal">RobotoFlex-Regular.ttf - <axis tag="slnt" stylevalue="0" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="100" /> - </font> - <font weight="200" style="normal">RobotoFlex-Regular.ttf - <axis tag="slnt" stylevalue="0" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="200" /> - </font> - <font weight="300" style="normal">RobotoFlex-Regular.ttf - <axis tag="slnt" stylevalue="0" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="300" /> - </font> - <font weight="400" style="normal">RobotoFlex-Regular.ttf - <axis tag="slnt" stylevalue="0" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="400" /> - </font> - <font weight="500" style="normal">RobotoFlex-Regular.ttf - <axis tag="slnt" stylevalue="0" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="500" /> - </font> - <font weight="600" style="normal">RobotoFlex-Regular.ttf - <axis tag="slnt" stylevalue="0" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="600" /> - </font> - <font weight="700" style="normal">RobotoFlex-Regular.ttf - <axis tag="slnt" stylevalue="0" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="700" /> - </font> - <font weight="800" style="normal">RobotoFlex-Regular.ttf - <axis tag="slnt" stylevalue="0" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="800" /> - </font> - <font weight="900" style="normal">RobotoFlex-Regular.ttf - <axis tag="slnt" stylevalue="0" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="900" /> - </font> - <font weight="100" style="italic">RobotoFlex-Regular.ttf - <axis tag="slnt" stylevalue="-10" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="100" /> - </font> - <font weight="200" style="italic">RobotoFlex-Regular.ttf - <axis tag="slnt" stylevalue="-10" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="200" /> - </font> - <font weight="300" style="italic">RobotoFlex-Regular.ttf - <axis tag="slnt" stylevalue="-10" /> + <family name="roboto-flex" varFamilyType="2"> + <font>RobotoFlex-Regular.ttf <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="300" /> - </font> - <font weight="400" style="italic">RobotoFlex-Regular.ttf - <axis tag="slnt" stylevalue="-10" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="400" /> - </font> - <font weight="500" style="italic">RobotoFlex-Regular.ttf - <axis tag="slnt" stylevalue="-10" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="500" /> - </font> - <font weight="600" style="italic">RobotoFlex-Regular.ttf - <axis tag="slnt" stylevalue="-10" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="600" /> - </font> - <font weight="700" style="italic">RobotoFlex-Regular.ttf - <axis tag="slnt" stylevalue="-10" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="700" /> - </font> - <font weight="800" style="italic">RobotoFlex-Regular.ttf - <axis tag="slnt" stylevalue="-10" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="800" /> - </font> - <font weight="900" style="italic">RobotoFlex-Regular.ttf - <axis tag="slnt" stylevalue="-10" /> - <axis tag="wdth" stylevalue="100" /> - <axis tag="wght" stylevalue="900" /> </font> </family> @@ -375,38 +109,12 @@ </font> <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font> </family> - <family lang="und-Ethi"> - <font weight="400" style="normal" postScriptName="NotoSansEthiopic-Regular"> - NotoSansEthiopic-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" postScriptName="NotoSansEthiopic-Regular"> + <family lang="und-Ethi" varFamilyType="1"> + <font postScriptName="NotoSansEthiopic-Regular"> NotoSansEthiopic-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" postScriptName="NotoSansEthiopic-Regular"> - NotoSansEthiopic-VF.ttf - <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal" postScriptName="NotoSansEthiopic-Regular"> - NotoSansEthiopic-VF.ttf - <axis tag="wght" stylevalue="700"/> - </font> - <font weight="400" style="normal" fallbackFor="serif" - postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" fallbackFor="serif" - postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" fallbackFor="serif" - postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" fallbackFor="serif" - postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf - <axis tag="wght" stylevalue="700"/> + <font fallbackFor="serif" postScriptName="NotoSerifEthiopic-Regular"> + NotoSerifEthiopic-VF.ttf </font> </family> <family lang="und-Hebr"> @@ -432,124 +140,33 @@ </font> <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font> </family> - <family lang="und-Armn"> - <font weight="400" style="normal" postScriptName="NotoSansArmenian-Regular"> - NotoSansArmenian-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" postScriptName="NotoSansArmenian-Regular"> - NotoSansArmenian-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" postScriptName="NotoSansArmenian-Regular"> - NotoSansArmenian-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" postScriptName="NotoSansArmenian-Regular"> + <family lang="und-Armn" varFamilyType="1"> + <font postScriptName="NotoSansArmenian-Regular"> NotoSansArmenian-VF.ttf - <axis tag="wght" stylevalue="700"/> </font> - <font weight="400" style="normal" fallbackFor="serif" - postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" fallbackFor="serif" - postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" fallbackFor="serif" - postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" fallbackFor="serif" - postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf - <axis tag="wght" stylevalue="700"/> + <font fallbackFor="serif" postScriptName="NotoSerifArmenian-Regular"> + NotoSerifArmenian-VF.ttf </font> </family> - <family lang="und-Geor,und-Geok"> - <font weight="400" style="normal" postScriptName="NotoSansGeorgian-Regular"> + <family lang="und-Geor,und-Geok" varFamilyType="1"> + <font postScriptName="NotoSansGeorgian-Regular"> NotoSansGeorgian-VF.ttf - <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal" postScriptName="NotoSansGeorgian-Regular"> - NotoSansGeorgian-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" postScriptName="NotoSansGeorgian-Regular"> - NotoSansGeorgian-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" postScriptName="NotoSansGeorgian-Regular"> - NotoSansGeorgian-VF.ttf - <axis tag="wght" stylevalue="700"/> - </font> - <font weight="400" style="normal" fallbackFor="serif" - postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" fallbackFor="serif" - postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" fallbackFor="serif" - postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" fallbackFor="serif" - postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf - <axis tag="wght" stylevalue="700"/> + <font fallbackFor="serif" postScriptName="NotoSerifGeorgian-Regular"> + NotoSerifGeorgian-VF.ttf </font> </family> - <family lang="und-Deva" variant="elegant"> - <font weight="400" style="normal" postScriptName="NotoSansDevanagari-Regular"> - NotoSansDevanagari-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" postScriptName="NotoSansDevanagari-Regular"> - NotoSansDevanagari-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" postScriptName="NotoSansDevanagari-Regular"> + <family lang="und-Deva" variant="elegant" varFamilyType="1"> + <font postScriptName="NotoSansDevanagari-Regular"> NotoSansDevanagari-VF.ttf - <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal" postScriptName="NotoSansDevanagari-Regular"> - NotoSansDevanagari-VF.ttf - <axis tag="wght" stylevalue="700"/> - </font> - <font weight="400" style="normal" fallbackFor="serif" - postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" fallbackFor="serif" - postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" fallbackFor="serif" - postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" fallbackFor="serif" - postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf - <axis tag="wght" stylevalue="700"/> + <font fallbackFor="serif" postScriptName="NotoSerifDevanagari-Regular"> + NotoSerifDevanagari-VF.ttf </font> </family> - <family lang="und-Deva" variant="compact"> - <font weight="400" style="normal" postScriptName="NotoSansDevanagariUI-Regular"> - NotoSansDevanagariUI-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" postScriptName="NotoSansDevanagariUI-Regular"> + <family lang="und-Deva" variant="compact" varFamilyType="1"> + <font postScriptName="NotoSansDevanagariUI-Regular"> NotoSansDevanagariUI-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" postScriptName="NotoSansDevanagariUI-Regular"> - NotoSansDevanagariUI-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" postScriptName="NotoSansDevanagariUI-Regular"> - NotoSansDevanagariUI-VF.ttf - <axis tag="wght" stylevalue="700"/> </font> </family> @@ -584,316 +201,82 @@ </font> <font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font> </family> - <family lang="und-Guru" variant="elegant"> - <font weight="400" style="normal" postScriptName="NotoSansGurmukhi-Regular"> + <family lang="und-Guru" variant="elegant" varFamilyType="1"> + <font postScriptName="NotoSansGurmukhi-Regular"> NotoSansGurmukhi-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" postScriptName="NotoSansGurmukhi-Regular"> - NotoSansGurmukhi-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" postScriptName="NotoSansGurmukhi-Regular"> - NotoSansGurmukhi-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" postScriptName="NotoSansGurmukhi-Regular"> - NotoSansGurmukhi-VF.ttf - <axis tag="wght" stylevalue="700"/> - </font> - <font weight="400" style="normal" fallbackFor="serif" - postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" fallbackFor="serif" - postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" fallbackFor="serif" - postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf - <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal" fallbackFor="serif" - postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf - <axis tag="wght" stylevalue="700"/> + <font fallbackFor="serif" postScriptName="NotoSerifGurmukhi-Regular"> + NotoSerifGurmukhi-VF.ttf </font> </family> - <family lang="und-Guru" variant="compact"> - <font weight="400" style="normal" postScriptName="NotoSansGurmukhiUI-Regular"> - NotoSansGurmukhiUI-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" postScriptName="NotoSansGurmukhiUI-Regular"> - NotoSansGurmukhiUI-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" postScriptName="NotoSansGurmukhiUI-Regular"> - NotoSansGurmukhiUI-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" postScriptName="NotoSansGurmukhiUI-Regular"> + <family lang="und-Guru" variant="compact" varFamilyType="1"> + <font postScriptName="NotoSansGurmukhiUI-Regular"> NotoSansGurmukhiUI-VF.ttf - <axis tag="wght" stylevalue="700"/> </font> </family> - <family lang="und-Taml" variant="elegant"> - <font weight="400" style="normal" postScriptName="NotoSansTamil-Regular"> - NotoSansTamil-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" postScriptName="NotoSansTamil-Regular"> + <family lang="und-Taml" variant="elegant" varFamilyType="1"> + <font postScriptName="NotoSansTamil-Regular"> NotoSansTamil-VF.ttf - <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal" postScriptName="NotoSansTamil-Regular"> - NotoSansTamil-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" postScriptName="NotoSansTamil-Regular"> - NotoSansTamil-VF.ttf - <axis tag="wght" stylevalue="700"/> - </font> - <font weight="400" style="normal" fallbackFor="serif" - postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" fallbackFor="serif" - postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" fallbackFor="serif" - postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" fallbackFor="serif" - postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf - <axis tag="wght" stylevalue="700"/> + <font fallbackFor="serif" postScriptName="NotoSerifTamil-Regular"> + NotoSerifTamil-VF.ttf </font> </family> - <family lang="und-Taml" variant="compact"> - <font weight="400" style="normal" postScriptName="NotoSansTamilUI-Regular"> - NotoSansTamilUI-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" postScriptName="NotoSansTamilUI-Regular"> - NotoSansTamilUI-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" postScriptName="NotoSansTamilUI-Regular"> + <family lang="und-Taml" variant="compact" varFamilyType="1"> + <font postScriptName="NotoSansTamilUI-Regular"> NotoSansTamilUI-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" postScriptName="NotoSansTamilUI-Regular"> - NotoSansTamilUI-VF.ttf - <axis tag="wght" stylevalue="700"/> </font> </family> - <family lang="und-Mlym" variant="elegant"> - <font weight="400" style="normal" postScriptName="NotoSansMalayalam-Regular"> - NotoSansMalayalam-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" postScriptName="NotoSansMalayalam-Regular"> + <family lang="und-Mlym" variant="elegant" varFamilyType="1"> + <font postScriptName="NotoSansMalayalam-Regular"> NotoSansMalayalam-VF.ttf - <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal" postScriptName="NotoSansMalayalam-Regular"> - NotoSansMalayalam-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" postScriptName="NotoSansMalayalam-Regular"> - NotoSansMalayalam-VF.ttf - <axis tag="wght" stylevalue="700"/> - </font> - <font weight="400" style="normal" fallbackFor="serif" - postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" fallbackFor="serif" - postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" fallbackFor="serif" - postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" fallbackFor="serif" - postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf - <axis tag="wght" stylevalue="700"/> + <font fallbackFor="serif" postScriptName="NotoSerifMalayalam-Regular"> + NotoSerifMalayalam-VF.ttf </font> </family> - <family lang="und-Mlym" variant="compact"> - <font weight="400" style="normal" postScriptName="NotoSansMalayalamUI-Regular"> - NotoSansMalayalamUI-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" postScriptName="NotoSansMalayalamUI-Regular"> - NotoSansMalayalamUI-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" postScriptName="NotoSansMalayalamUI-Regular"> - NotoSansMalayalamUI-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" postScriptName="NotoSansMalayalamUI-Regular"> + <family lang="und-Mlym" variant="compact" varFamilyType="1"> + <font postScriptName="NotoSansMalayalamUI-Regular"> NotoSansMalayalamUI-VF.ttf - <axis tag="wght" stylevalue="700"/> </font> </family> - <family lang="und-Beng" variant="elegant"> - <font weight="400" style="normal" postScriptName="NotoSansBengali-Regular"> - NotoSansBengali-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" postScriptName="NotoSansBengali-Regular"> + <family lang="und-Beng" variant="elegant" varFamilyType="1"> + <font postScriptName="NotoSansBengali-Regular"> NotoSansBengali-VF.ttf - <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal" postScriptName="NotoSansBengali-Regular"> - NotoSansBengali-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" postScriptName="NotoSansBengali-Regular"> - NotoSansBengali-VF.ttf - <axis tag="wght" stylevalue="700"/> - </font> - <font weight="400" style="normal" fallbackFor="serif" - postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" fallbackFor="serif" - postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" fallbackFor="serif" - postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" fallbackFor="serif" - postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf - <axis tag="wght" stylevalue="700"/> + <font fallbackFor="serif" postScriptName="NotoSerifBengali-Regular"> + NotoSerifBengali-VF.ttf </font> </family> - <family lang="und-Beng" variant="compact"> - <font weight="400" style="normal" postScriptName="NotoSansBengaliUI-Regular"> - NotoSansBengaliUI-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" postScriptName="NotoSansBengaliUI-Regular"> - NotoSansBengaliUI-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" postScriptName="NotoSansBengaliUI-Regular"> - NotoSansBengaliUI-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" postScriptName="NotoSansBengaliUI-Regular"> + <family lang="und-Beng" variant="compact" varFamilyType="1"> + <font postScriptName="NotoSansBengaliUI-Regular"> NotoSansBengaliUI-VF.ttf - <axis tag="wght" stylevalue="700"/> </font> </family> - <family lang="und-Telu" variant="elegant"> - <font weight="400" style="normal" postScriptName="NotoSansTelugu-Regular"> - NotoSansTelugu-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" postScriptName="NotoSansTelugu-Regular"> + <family lang="und-Telu" variant="elegant" varFamilyType="1"> + <font postScriptName="NotoSansTelugu-Regular"> NotoSansTelugu-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" postScriptName="NotoSansTelugu-Regular"> - NotoSansTelugu-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" postScriptName="NotoSansTelugu-Regular"> - NotoSansTelugu-VF.ttf - <axis tag="wght" stylevalue="700"/> - </font> - <font weight="400" style="normal" fallbackFor="serif" - postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" fallbackFor="serif" - postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf - <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal" fallbackFor="serif" - postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" fallbackFor="serif" - postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf - <axis tag="wght" stylevalue="700"/> + <font fallbackFor="serif" postScriptName="NotoSerifTelugu-Regular"> + NotoSerifTelugu-VF.ttf </font> </family> - <family lang="und-Telu" variant="compact"> - <font weight="400" style="normal" postScriptName="NotoSansTeluguUI-Regular"> - NotoSansTeluguUI-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" postScriptName="NotoSansTeluguUI-Regular"> - NotoSansTeluguUI-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" postScriptName="NotoSansTeluguUI-Regular"> + <family lang="und-Telu" variant="compact" varFamilyType="1"> + <font postScriptName="NotoSansTeluguUI-Regular"> NotoSansTeluguUI-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" postScriptName="NotoSansTeluguUI-Regular"> - NotoSansTeluguUI-VF.ttf - <axis tag="wght" stylevalue="700"/> </font> </family> - <family lang="und-Knda" variant="elegant"> - <font weight="400" style="normal" postScriptName="NotoSansKannada-Regular"> + <family lang="und-Knda" variant="elegant" varFamilyType="1"> + <font postScriptName="NotoSansKannada-Regular"> NotoSansKannada-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" postScriptName="NotoSansKannada-Regular"> - NotoSansKannada-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" postScriptName="NotoSansKannada-Regular"> - NotoSansKannada-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" postScriptName="NotoSansKannada-Regular"> - NotoSansKannada-VF.ttf - <axis tag="wght" stylevalue="700"/> - </font> - <font weight="400" style="normal" fallbackFor="serif" - postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" fallbackFor="serif" - postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" fallbackFor="serif" - postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf - <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal" fallbackFor="serif" - postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf - <axis tag="wght" stylevalue="700"/> + <font fallbackFor="serif" postScriptName="NotoSerifKannada-Regular"> + NotoSerifKannada-VF.ttf </font> </family> - <family lang="und-Knda" variant="compact"> - <font weight="400" style="normal" postScriptName="NotoSansKannadaUI-Regular"> - NotoSansKannadaUI-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" postScriptName="NotoSansKannadaUI-Regular"> + <family lang="und-Knda" variant="compact" varFamilyType="1"> + <font postScriptName="NotoSansKannadaUI-Regular"> NotoSansKannadaUI-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" postScriptName="NotoSansKannadaUI-Regular"> - NotoSansKannadaUI-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" postScriptName="NotoSansKannadaUI-Regular"> - NotoSansKannadaUI-VF.ttf - <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Orya" variant="elegant"> @@ -907,56 +290,17 @@ </font> <font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font> </family> - <family lang="und-Sinh" variant="elegant"> - <font weight="400" style="normal" postScriptName="NotoSansSinhala-Regular"> + <family lang="und-Sinh" variant="elegant" varFamilyType="1"> + <font postScriptName="NotoSansSinhala-Regular"> NotoSansSinhala-VF.ttf - <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal" postScriptName="NotoSansSinhala-Regular"> - NotoSansSinhala-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" postScriptName="NotoSansSinhala-Regular"> - NotoSansSinhala-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" postScriptName="NotoSansSinhala-Regular"> - NotoSansSinhala-VF.ttf - <axis tag="wght" stylevalue="700"/> - </font> - <font weight="400" style="normal" fallbackFor="serif" - postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" fallbackFor="serif" - postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" fallbackFor="serif" - postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" fallbackFor="serif" - postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf - <axis tag="wght" stylevalue="700"/> + <font fallbackFor="serif" postScriptName="NotoSerifSinhala-Regular"> + NotoSerifSinhala-VF.ttf </font> </family> - <family lang="und-Sinh" variant="compact"> - <font weight="400" style="normal" postScriptName="NotoSansSinhalaUI-Regular"> - NotoSansSinhalaUI-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" postScriptName="NotoSansSinhalaUI-Regular"> - NotoSansSinhalaUI-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" postScriptName="NotoSansSinhalaUI-Regular"> - NotoSansSinhalaUI-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" postScriptName="NotoSansSinhalaUI-Regular"> + <family lang="und-Sinh" variant="compact" varFamilyType="1"> + <font postScriptName="NotoSansSinhalaUI-Regular"> NotoSansSinhalaUI-VF.ttf - <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Khmr" variant="elegant"> @@ -1054,22 +398,9 @@ <family lang="und-Ahom"> <font weight="400" style="normal">NotoSansAhom-Regular.otf</font> </family> - <family lang="und-Adlm"> - <font weight="400" style="normal" postScriptName="NotoSansAdlam-Regular"> - NotoSansAdlam-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" postScriptName="NotoSansAdlam-Regular"> + <family lang="und-Adlm" varFamilyType="1"> + <font postScriptName="NotoSansAdlam-Regular"> NotoSansAdlam-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" postScriptName="NotoSansAdlam-Regular"> - NotoSansAdlam-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" postScriptName="NotoSansAdlam-Regular"> - NotoSansAdlam-VF.ttf - <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Avst"> @@ -1355,22 +686,9 @@ NotoSansTaiViet-Regular.ttf </font> </family> - <family lang="und-Tibt"> - <font weight="400" style="normal" postScriptName="NotoSerifTibetan-Regular"> + <family lang="und-Tibt" varFamilyType="1"> + <font postScriptName="NotoSerifTibetan-Regular"> NotoSerifTibetan-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" postScriptName="NotoSerifTibetan-Regular"> - NotoSerifTibetan-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" postScriptName="NotoSerifTibetan-Regular"> - NotoSerifTibetan-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" postScriptName="NotoSerifTibetan-Regular"> - NotoSerifTibetan-VF.ttf - <axis tag="wght" stylevalue="700"/> </font> </family> <family lang="und-Tfng"> @@ -1537,94 +855,29 @@ <family lang="und-Dogr"> <font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font> </family> - <family lang="und-Medf"> - <font weight="400" style="normal" postScriptName="NotoSansMedefaidrin-Regular"> - NotoSansMedefaidrin-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" postScriptName="NotoSansMedefaidrin-Regular"> - NotoSansMedefaidrin-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" postScriptName="NotoSansMedefaidrin-Regular"> - NotoSansMedefaidrin-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" postScriptName="NotoSansMedefaidrin-Regular"> + <family lang="und-Medf" varFamilyType="1"> + <font postScriptName="NotoSansMedefaidrin-Regular"> NotoSansMedefaidrin-VF.ttf - <axis tag="wght" stylevalue="700"/> </font> </family> - <family lang="und-Soyo"> - <font weight="400" style="normal" postScriptName="NotoSansSoyombo-Regular"> - NotoSansSoyombo-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" postScriptName="NotoSansSoyombo-Regular"> + <family lang="und-Soyo" varFamilyType="1"> + <font postScriptName="NotoSansSoyombo-Regular"> NotoSansSoyombo-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" postScriptName="NotoSansSoyombo-Regular"> - NotoSansSoyombo-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" postScriptName="NotoSansSoyombo-Regular"> - NotoSansSoyombo-VF.ttf - <axis tag="wght" stylevalue="700"/> </font> </family> - <family lang="und-Takr"> - <font weight="400" style="normal" postScriptName="NotoSansTakri-Regular"> + <family lang="und-Takr" varFamilyType="1"> + <font postScriptName="NotoSansTakri-Regular"> NotoSansTakri-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" postScriptName="NotoSansTakri-Regular"> - NotoSansTakri-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" postScriptName="NotoSansTakri-Regular"> - NotoSansTakri-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" postScriptName="NotoSansTakri-Regular"> - NotoSansTakri-VF.ttf - <axis tag="wght" stylevalue="700"/> </font> </family> - <family lang="und-Hmnp"> - <font weight="400" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular"> - NotoSerifNyiakengPuachueHmong-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular"> - NotoSerifNyiakengPuachueHmong-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular"> - NotoSerifNyiakengPuachueHmong-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular"> + <family lang="und-Hmnp" varFamilyType="1"> + <font postScriptName="NotoSerifHmongNyiakeng-Regular"> NotoSerifNyiakengPuachueHmong-VF.ttf - <axis tag="wght" stylevalue="700"/> </font> </family> - <family lang="und-Yezi"> - <font weight="400" style="normal" postScriptName="NotoSerifYezidi-Regular"> - NotoSerifYezidi-VF.ttf - <axis tag="wght" stylevalue="400"/> - </font> - <font weight="500" style="normal" postScriptName="NotoSerifYezidi-Regular"> + <family lang="und-Yezi" varFamilyType="1"> + <font postScriptName="NotoSerifYezidi-Regular"> NotoSerifYezidi-VF.ttf - <axis tag="wght" stylevalue="500"/> - </font> - <font weight="600" style="normal" postScriptName="NotoSerifYezidi-Regular"> - NotoSerifYezidi-VF.ttf - <axis tag="wght" stylevalue="600"/> - </font> - <font weight="700" style="normal" postScriptName="NotoSerifYezidi-Regular"> - NotoSerifYezidi-VF.ttf - <axis tag="wght" stylevalue="700"/> </font> </family> </familyset> diff --git a/data/keyboards/Vendor_0957_Product_0001.kl b/data/keyboards/Vendor_0957_Product_0001.kl index 354f10a9432a..87cb942602f7 100644 --- a/data/keyboards/Vendor_0957_Product_0001.kl +++ b/data/keyboards/Vendor_0957_Product_0001.kl @@ -47,7 +47,6 @@ key usage 0x00070037 PERIOD # custom keys key usage 0x000c01BB TV_INPUT -key usage 0x000c0186 MACRO_1 WAKE key usage 0x000c0185 TV_TELETEXT key usage 0x000c0061 CAPTIONS diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 1ff5a3d46f8e..250362b1e1e3 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -479,7 +479,8 @@ public final class Bitmap implements Parcelable { * This configuration may be useful when using opaque bitmaps * that do not require high color fidelity. * - * <p>Use this formula to pack into 16 bits:</p> + * <p>When accessing directly via #copyPixelsFromBuffer or #copyPixelsToBuffer, + * use this formula to pack into 16 bits:</p> * <pre class="prettyprint"> * short color = (R & 0x1f) << 11 | (G & 0x3f) << 5 | (B & 0x1f); * </pre> @@ -516,7 +517,8 @@ public final class Bitmap implements Parcelable { * This configuration is very flexible and offers the best * quality. It should be used whenever possible. * - * <p>Use this formula to pack into 32 bits:</p> + * <p>When accessing directly via #copyPixelsFromBuffer or #copyPixelsToBuffer, + * use this formula to pack into 32 bits:</p> * <pre class="prettyprint"> * int color = (A & 0xff) << 24 | (B & 0xff) << 16 | (G & 0xff) << 8 | (R & 0xff); * </pre> @@ -531,7 +533,8 @@ public final class Bitmap implements Parcelable { * This configuration is particularly suited for wide-gamut and * HDR content. * - * <p>Use this formula to pack into 64 bits:</p> + * <p>When accessing directly via #copyPixelsFromBuffer or #copyPixelsToBuffer, + * use this formula to pack into 64 bits:</p> * <pre class="prettyprint"> * long color = (A & 0xffff) << 48 | (B & 0xffff) << 32 | (G & 0xffff) << 16 | (R & 0xffff); * </pre> @@ -556,7 +559,8 @@ public final class Bitmap implements Parcelable { * blending, such that the memory cost is the same as ARGB_8888 while enabling higher color * precision. * - * <p>Use this formula to pack into 32 bits:</p> + * <p>When accessing directly via #copyPixelsFromBuffer or #copyPixelsToBuffer, + * use this formula to pack into 32 bits:</p> * <pre class="prettyprint"> * int color = (A & 0x3) << 30 | (B & 0x3ff) << 20 | (G & 0x3ff) << 10 | (R & 0x3ff); * </pre> diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java index 5c065775eea2..dcfff62459ab 100644 --- a/graphics/java/android/graphics/BitmapShader.java +++ b/graphics/java/android/graphics/BitmapShader.java @@ -16,9 +16,13 @@ package android.graphics; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.graphics.hwui.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -32,6 +36,7 @@ public class BitmapShader extends Shader { * Prevent garbage collection. */ /*package*/ Bitmap mBitmap; + private Gainmap mOverrideGainmap; private int mTileX; private int mTileY; @@ -173,6 +178,24 @@ public class BitmapShader extends Shader { } /** + * Draws the BitmapShader with a copy of the given gainmap instead of the gainmap on the Bitmap + * the shader was constructed from + * + * @param overrideGainmap The gainmap to draw instead, null to use any gainmap on the Bitmap + */ + @FlaggedApi(Flags.FLAG_GAINMAP_ANIMATIONS) + public void setOverrideGainmap(@Nullable Gainmap overrideGainmap) { + if (!Flags.gainmapAnimations()) throw new IllegalStateException("API not available"); + + if (overrideGainmap == null) { + mOverrideGainmap = null; + } else { + mOverrideGainmap = new Gainmap(overrideGainmap, overrideGainmap.getGainmapContents()); + } + discardNativeInstance(); + } + + /** * Returns the current max anisotropic filtering value configured by * {@link #setFilterMode(int)}. If {@link #setFilterMode(int)} is invoked this returns zero. */ @@ -199,14 +222,9 @@ public class BitmapShader extends Shader { mIsDirectSampled = mRequestDirectSampling; mRequestDirectSampling = false; - - if (mMaxAniso > 0) { - return nativeCreateWithMaxAniso(nativeMatrix, mBitmap.getNativeInstance(), mTileX, - mTileY, mMaxAniso, mIsDirectSampled); - } else { - return nativeCreate(nativeMatrix, mBitmap.getNativeInstance(), mTileX, mTileY, - enableLinearFilter, mIsDirectSampled); - } + return nativeCreate(nativeMatrix, mBitmap.getNativeInstance(), mTileX, + mTileY, mMaxAniso, enableLinearFilter, mIsDirectSampled, + mOverrideGainmap != null ? mOverrideGainmap.mNativePtr : 0); } /** @hide */ @@ -217,9 +235,7 @@ public class BitmapShader extends Shader { } private static native long nativeCreate(long nativeMatrix, long bitmapHandle, - int shaderTileModeX, int shaderTileModeY, boolean filter, boolean isDirectSampled); - - private static native long nativeCreateWithMaxAniso(long nativeMatrix, long bitmapHandle, - int shaderTileModeX, int shaderTileModeY, int maxAniso, boolean isDirectSampled); + int shaderTileModeX, int shaderTileModeY, int maxAniso, boolean filter, + boolean isDirectSampled, long overrideGainmapHandle); } diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index 674246acafef..735bc180c015 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -16,6 +16,10 @@ package android.graphics; +import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE; +import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL; +import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY; +import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT; import static android.text.FontConfig.NamedFamilyList; import android.annotation.NonNull; @@ -28,6 +32,7 @@ import android.os.Build; import android.os.LocaleList; import android.text.FontConfig; import android.util.ArraySet; +import android.util.Log; import android.util.Xml; import org.xmlpull.v1.XmlPullParser; @@ -256,6 +261,7 @@ public class FontListParser { final String lang = parser.getAttributeValue("", "lang"); final String variant = parser.getAttributeValue(null, "variant"); final String ignore = parser.getAttributeValue(null, "ignore"); + final String varFamilyTypeStr = parser.getAttributeValue(null, "varFamilyType"); final List<FontConfig.Font> fonts = new ArrayList<>(); while (keepReading(parser)) { if (parser.getEventType() != XmlPullParser.START_TAG) continue; @@ -278,12 +284,45 @@ public class FontListParser { intVariant = FontConfig.FontFamily.VARIANT_ELEGANT; } } + int varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE; + if (varFamilyTypeStr != null) { + varFamilyType = Integer.parseInt(varFamilyTypeStr); + if (varFamilyType <= -1 || varFamilyType > 3) { + Log.e(TAG, "Error: unexpected varFamilyType value: " + varFamilyTypeStr); + varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE; + } + + // validation but don't read font content for performance reasons. + switch (varFamilyType) { + case VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY: + if (fonts.size() != 1) { + Log.e(TAG, "Error: Single font support wght axis, but two or more fonts are" + + " included in the font family."); + varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE; + } + break; + case VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL: + if (fonts.size() != 1) { + Log.e(TAG, "Error: Single font support both ital and wght axes, but two or" + + " more fonts are included in the font family."); + varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE; + } + break; + case VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT: + if (fonts.size() != 2) { + Log.e(TAG, "Error: two fonts that support wght axis, but one or three or" + + " more fonts are included in the font family."); + varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE; + } + } + } boolean skip = (ignore != null && (ignore.equals("true") || ignore.equals("1"))); if (skip || fonts.isEmpty()) { return null; } - return new FontConfig.FontFamily(fonts, LocaleList.forLanguageTags(lang), intVariant); + return new FontConfig.FontFamily(fonts, LocaleList.forLanguageTags(lang), intVariant, + varFamilyType); } private static void throwIfAttributeExists(String attrName, XmlPullParser parser) { diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java index 5e4110590325..4c753565eb5b 100644 --- a/graphics/java/android/graphics/fonts/FontFamily.java +++ b/graphics/java/android/graphics/fonts/FontFamily.java @@ -18,7 +18,10 @@ package android.graphics.fonts; import static com.android.text.flags.Flags.FLAG_DEPRECATE_FONTS_XML; +import static java.lang.annotation.RetentionPolicy.SOURCE; + import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -32,6 +35,7 @@ import dalvik.annotation.optimization.FastNative; import libcore.util.NativeAllocationRegistry; +import java.lang.annotation.Retention; import java.util.ArrayList; import java.util.Set; @@ -184,32 +188,59 @@ public final class FontFamily { } /** + * A special variable font family type that indicates `analyzeAndResolveVariableType` could + * not be identified the variable font family type. + * * @see #buildVariableFamily() * @hide */ public static final int VARIABLE_FONT_FAMILY_TYPE_UNKNOWN = -1; /** + * A variable font family type that indicates no variable font family can be used. + * + * The font family is used as bundle of static fonts. * @see #buildVariableFamily() * @hide */ public static final int VARIABLE_FONT_FAMILY_TYPE_NONE = 0; /** + * A variable font family type that indicates single font file can be used for multiple + * weight. For the italic style, fake italic may be applied. + * * @see #buildVariableFamily() * @hide */ public static final int VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY = 1; /** + * A variable font family type that indicates single font file can be used for multiple + * weight and italic. + * * @see #buildVariableFamily() * @hide */ public static final int VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL = 2; /** + * A variable font family type that indicates two font files are included in the family: + * one can be used for upright with various weights, the other one can be used for italic + * with various weights. + * * @see #buildVariableFamily() * @hide */ public static final int VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT = 3; + /** @hide */ + @Retention(SOURCE) + @IntDef(prefix = { "VARIABLE_FONT_FAMILY_TYPE_" }, value = { + VARIABLE_FONT_FAMILY_TYPE_UNKNOWN, + VARIABLE_FONT_FAMILY_TYPE_NONE, + VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY, + VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL, + VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT + }) + public @interface VariableFontFamilyType {} + /** * The registered italic axis used for adjusting requested style. * https://learn.microsoft.com/en-us/typography/opentype/spec/dvaraxistag_ital @@ -222,7 +253,9 @@ public final class FontFamily { */ private static final int TAG_wght = 0x77676874; // w(0x77), g(0x67), h(0x68), t(0x74) - private static int analyzeAndResolveVariableType(ArrayList<Font> fonts) { + /** @hide */ + public static @VariableFontFamilyType int analyzeAndResolveVariableType( + ArrayList<Font> fonts) { if (fonts.size() > 2) { return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN; } diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java index 9810022abfed..d4e35b30c8d0 100644 --- a/graphics/java/android/graphics/fonts/SystemFonts.java +++ b/graphics/java/android/graphics/fonts/SystemFonts.java @@ -121,7 +121,8 @@ public final class SystemFonts { final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily( - defaultFonts, languageTags, variant, false, cache); + defaultFonts, languageTags, variant, xmlFamily.getVariableFontFamilyType(), false, + cache); // Insert family into fallback map. for (int i = 0; i < fallbackMap.size(); i++) { final String name = fallbackMap.keyAt(i); @@ -138,8 +139,8 @@ public final class SystemFonts { familyListSet.familyList.add(defaultFamily); } } else { - final FontFamily family = createFontFamily(fallback, languageTags, variant, false, - cache); + final FontFamily family = createFontFamily(fallback, languageTags, variant, + xmlFamily.getVariableFontFamilyType(), false, cache); if (family != null) { familyListSet.familyList.add(family); } else if (defaultFamily != null) { @@ -155,6 +156,7 @@ public final class SystemFonts { @NonNull List<FontConfig.Font> fonts, @NonNull String languageTags, @FontConfig.FontFamily.Variant int variant, + int varFamilyType, boolean isDefaultFallback, @NonNull Map<String, ByteBuffer> cache) { if (fonts.size() == 0) { @@ -196,7 +198,7 @@ public final class SystemFonts { } } return b == null ? null : b.build(languageTags, variant, false /* isCustomFallback */, - isDefaultFallback, FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE); + isDefaultFallback, varFamilyType); } private static void appendNamedFamilyList(@NonNull FontConfig.NamedFamilyList namedFamilyList, @@ -210,6 +212,7 @@ public final class SystemFonts { final FontFamily family = createFontFamily( xmlFamily.getFontList(), xmlFamily.getLocaleList().toLanguageTags(), xmlFamily.getVariant(), + xmlFamily.getVariableFontFamilyType(), true, // named family is always default bufferCache); if (family == null) { @@ -291,6 +294,7 @@ public final class SystemFonts { int configVersion ) { try { + Log.i(TAG, "Loading font config from " + fontsXml); return FontListParser.parse(fontsXml, systemFontDir, oemXml, productFontDir, updatableFontMap, lastModifiedDate, configVersion); } catch (IOException e) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index f259902e9565..dddcbd4c96c0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -512,6 +512,7 @@ public class BubbleController implements ConfigurationChangeListener, * <p>If bubble bar is supported, bubble views will be updated to switch to bar mode. */ public void registerBubbleStateListener(Bubbles.BubbleStateListener listener) { + mBubbleProperties.refresh(); if (canShowAsBubbleBar() && listener != null) { // Only set the listener if we can show the bubble bar. mBubbleStateListener = listener; @@ -529,6 +530,7 @@ public class BubbleController implements ConfigurationChangeListener, * will be updated accordingly. */ public void unregisterBubbleStateListener() { + mBubbleProperties.refresh(); if (mBubbleStateListener != null) { mBubbleStateListener = null; setUpBubbleViewsForMode(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt index 85aaa8ef585c..4206d9320b7d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt @@ -29,4 +29,7 @@ interface BubbleProperties { * When this is `false`, bubbles will be floating. */ val isBubbleBarEnabled: Boolean + + /** Refreshes the current value of [isBubbleBarEnabled]. */ + fun refresh() } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt index 9d8b9a6f3260..e1dea3babbc2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt @@ -22,6 +22,13 @@ import android.os.SystemProperties object ProdBubbleProperties : BubbleProperties { // TODO(b/256873975) Should use proper flag when available to shell/launcher - override val isBubbleBarEnabled = - SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false) + private var _isBubbleBarEnabled = + SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false) + + override val isBubbleBarEnabled + get() = _isBubbleBarEnabled + + override fun refresh() { + _isBubbleBarEnabled = SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false) + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index 26b5a5052594..63cdb4f151ff 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -67,6 +67,7 @@ import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.InteractionJankMonitorUtils; +import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition; import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; @@ -484,7 +485,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange } /** Updates divide position and split bounds base on the ratio within root bounds. */ - public void setDivideRatio(@SnapPosition int snapPosition) { + public void setDivideRatio(@PersistentSnapPosition int snapPosition) { final DividerSnapAlgorithm.SnapTarget snapTarget = mDividerSnapAlgorithm.findSnapTarget( snapPosition); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java index ff38b7e70410..e73430056c89 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java @@ -26,6 +26,16 @@ import android.annotation.IntDef; /** Helper utility class of methods and constants that are available to be imported in Launcher. */ public class SplitScreenConstants { + /** + * Duration used for every split fade-in or fade-out. + */ + public static final int FADE_DURATION = 133; + + /////////////// + // IMPORTANT for the following SPLIT_POSITION and SNAP_TO constants: + // These int values must not be changed -- they are persisted to user-defined app pairs, and + // will break things if changed. + // /** * Split position isn't specified normally meaning to use what ever it is currently set to. @@ -44,11 +54,6 @@ public class SplitScreenConstants { */ public static final int SPLIT_POSITION_BOTTOM_OR_RIGHT = 1; - /** - * Duration used for every split fade-in or fade-out. - */ - public static final int FADE_DURATION = 133; - @IntDef(prefix = {"SPLIT_POSITION_"}, value = { SPLIT_POSITION_UNDEFINED, SPLIT_POSITION_TOP_OR_LEFT, @@ -57,38 +62,61 @@ public class SplitScreenConstants { public @interface SplitPosition { } - /** The divider doesn't snap to any target and is freely placeable. */ - public static final int SNAP_TO_NONE = 0; - - /** A snap target positioned near the screen edge for a minimized task */ - public static final int SNAP_TO_MINIMIZE = 1; - - /** If the divider reaches this value, the left/top task should be dismissed. */ - public static final int SNAP_TO_START_AND_DISMISS = 2; - /** A snap target in the first half of the screen, where the split is roughly 30-70. */ - public static final int SNAP_TO_30_70 = 3; + public static final int SNAP_TO_30_70 = 0; /** The 50-50 snap target */ - public static final int SNAP_TO_50_50 = 4; + public static final int SNAP_TO_50_50 = 1; /** A snap target in the latter half of the screen, where the split is roughly 70-30. */ - public static final int SNAP_TO_70_30 = 5; + public static final int SNAP_TO_70_30 = 2; + + /** + * These snap targets are used for split pairs in a stable, non-transient state. They may be + * persisted in Launcher when the user saves an app pair. They are a subset of + * {@link SnapPosition}. + */ + @IntDef(prefix = { "SNAP_TO_" }, value = { + SNAP_TO_30_70, + SNAP_TO_50_50, + SNAP_TO_70_30 + }) + public @interface PersistentSnapPosition {} + + /** + * Checks if the snapPosition in question is a {@link PersistentSnapPosition}. + */ + public static boolean isPersistentSnapPosition(@SnapPosition int snapPosition) { + return snapPosition == SNAP_TO_30_70 + || snapPosition == SNAP_TO_50_50 + || snapPosition == SNAP_TO_70_30; + } + + /** The divider doesn't snap to any target and is freely placeable. */ + public static final int SNAP_TO_NONE = 10; + + /** If the divider reaches this value, the left/top task should be dismissed. */ + public static final int SNAP_TO_START_AND_DISMISS = 11; /** If the divider reaches this value, the right/bottom task should be dismissed. */ - public static final int SNAP_TO_END_AND_DISMISS = 6; + public static final int SNAP_TO_END_AND_DISMISS = 12; + + /** A snap target positioned near the screen edge for a minimized task */ + public static final int SNAP_TO_MINIMIZE = 13; @IntDef(prefix = { "SNAP_TO_" }, value = { - SNAP_TO_NONE, - SNAP_TO_MINIMIZE, - SNAP_TO_START_AND_DISMISS, SNAP_TO_30_70, SNAP_TO_50_50, SNAP_TO_70_30, - SNAP_TO_END_AND_DISMISS + SNAP_TO_NONE, + SNAP_TO_START_AND_DISMISS, + SNAP_TO_END_AND_DISMISS, + SNAP_TO_MINIMIZE }) public @interface SnapPosition {} + /////////////// + public static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD}; public static final int[] CONTROLLED_WINDOWING_MODES = {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED}; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 9f9854e7e244..11aa054676cb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -199,6 +199,7 @@ public abstract class WMShellModule { @ShellMainThread Handler mainHandler, @ShellMainThread Choreographer mainChoreographer, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ShellTaskOrganizer taskOrganizer, DisplayController displayController, ShellController shellController, @@ -213,6 +214,7 @@ public abstract class WMShellModule { mainHandler, mainChoreographer, shellInit, + shellCommandHandler, taskOrganizer, displayController, shellController, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index ccffa02a22c1..664d44910e72 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -23,6 +23,7 @@ import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; + import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; @@ -85,7 +86,7 @@ import com.android.wm.shell.common.SingleInstanceRemoteListener; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.annotations.ExternalThread; -import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition; +import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.common.split.SplitScreenUtils; import com.android.wm.shell.desktopmode.DesktopTasksController; @@ -601,7 +602,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, - @SplitPosition int splitPosition, @SnapPosition int snapPosition, + @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) { if (options1 == null) options1 = new Bundle(); final ActivityOptions activityOptions = ActivityOptions.fromBundle(options1); @@ -632,7 +633,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, - @SnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, + @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { if (options1 == null) options1 = new Bundle(); final ActivityOptions activityOptions = ActivityOptions.fromBundle(options1); @@ -675,7 +676,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, - @SplitPosition int splitPosition, @SnapPosition int snapPosition, + @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) { Intent fillInIntent = null; final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent); @@ -702,7 +703,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, private void startIntentAndTask(PendingIntent pendingIntent, int userId1, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, - @SplitPosition int splitPosition, @SnapPosition int snapPosition, + @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { Intent fillInIntent = null; final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent); @@ -736,7 +737,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2, @SplitPosition int splitPosition, - @SnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) { + @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, + InstanceId instanceId) { Intent fillInIntent1 = null; Intent fillInIntent2 = null; final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1); @@ -767,7 +769,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2, @SplitPosition int splitPosition, - @SnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, + @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { Intent fillInIntent1 = null; Intent fillInIntent2 = null; @@ -1225,7 +1227,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Override public void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition, - @SnapPosition int snapPosition, RemoteAnimationAdapter adapter, + @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startTasks", (controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition( @@ -1236,7 +1238,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Override public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1, Bundle options1, int taskId, Bundle options2, int splitPosition, - @SnapPosition int snapPosition, RemoteAnimationAdapter adapter, + @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startIntentAndTaskWithLegacyTransition", (controller) -> @@ -1248,7 +1250,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Override public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, - @SplitPosition int splitPosition, @SnapPosition int snapPosition, + @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startShortcutAndTaskWithLegacyTransition", (controller) -> @@ -1260,8 +1262,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Override public void startTasks(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition, - @SnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, - InstanceId instanceId) { + @PersistentSnapPosition int snapPosition, + @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startTasks", (controller) -> controller.mStageCoordinator.startTasks(taskId1, options1, taskId2, options2, splitPosition, snapPosition, remoteTransition, @@ -1271,7 +1273,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Override public void startIntentAndTask(PendingIntent pendingIntent, int userId1, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, - @SplitPosition int splitPosition, @SnapPosition int snapPosition, + @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startIntentAndTask", (controller) -> controller.startIntentAndTask(pendingIntent, userId1, options1, @@ -1282,8 +1284,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Override public void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, - @SnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, - InstanceId instanceId) { + @PersistentSnapPosition int snapPosition, + @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startShortcutAndTask", (controller) -> controller.startShortcutAndTask(shortcutInfo, options1, taskId, options2, splitPosition, snapPosition, remoteTransition, instanceId)); @@ -1294,7 +1296,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2, @SplitPosition int splitPosition, - @SnapPosition int snapPosition, RemoteAnimationAdapter adapter, + @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startIntentsWithLegacyTransition", (controller) -> @@ -1309,8 +1311,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2, @SplitPosition int splitPosition, - @SnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, - InstanceId instanceId) { + @PersistentSnapPosition int snapPosition, + @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startIntents", (controller) -> controller.startIntents(pendingIntent1, userId1, shortcutInfo1, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 4ea14f473c39..5e2c61b9d3cd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -128,7 +128,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.split.SplitLayout; -import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition; +import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.common.split.SplitScreenUtils; import com.android.wm.shell.common.split.SplitWindowManager; @@ -633,7 +633,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Starts 2 tasks in one transition. */ void startTasks(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2, - @SplitPosition int splitPosition, @SnapPosition int snapPosition, + @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (taskId2 == INVALID_TASK_ID) { @@ -661,7 +661,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Start an intent and a task to a split pair in one transition. */ void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, - @SplitPosition int splitPosition, @SnapPosition int snapPosition, + @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (taskId == INVALID_TASK_ID) { @@ -683,7 +683,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Starts a shortcut and a task to a split pair in one transition. */ void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, - @SnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, + @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (taskId == INVALID_TASK_ID) { @@ -710,7 +710,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, * {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)} */ private void startWithTask(WindowContainerTransaction wct, int mainTaskId, - @Nullable Bundle mainOptions, @SnapPosition int snapPosition, + @Nullable Bundle mainOptions, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { if (!mMainStage.isActive()) { // Build a request WCT that will launch both apps such that task 0 is on the main stage @@ -744,7 +744,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, PendingIntent pendingIntent2, Intent fillInIntent2, @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2, - @SplitPosition int splitPosition, @SnapPosition int snapPosition, + @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (pendingIntent2 == null) { @@ -796,7 +796,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Starts a pair of tasks using legacy transition. */ void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition, - @SnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) { + @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, + InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (options1 == null) options1 = new Bundle(); if (taskId2 == INVALID_TASK_ID) { @@ -826,7 +827,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, @Nullable PendingIntent pendingIntent2, Intent fillInIntent2, @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2, - @SplitPosition int splitPosition, @SnapPosition int snapPosition, + @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (options1 == null) options1 = new Bundle(); @@ -851,7 +852,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, - @SplitPosition int splitPosition, @SnapPosition int snapPosition, + @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (options1 == null) options1 = new Bundle(); @@ -872,7 +873,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Starts a pair of shortcut and task using legacy transition. */ void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, - @SplitPosition int splitPosition, @SnapPosition int snapPosition, + @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (options1 == null) options1 = new Bundle(); @@ -934,7 +935,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void startWithLegacyTransition(WindowContainerTransaction wct, @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent, @Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle mainOptions, - @SplitPosition int sidePosition, @SnapPosition int snapPosition, + @SplitPosition int sidePosition, @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) { startWithLegacyTransition(wct, INVALID_TASK_ID, mainPendingIntent, mainFillInIntent, mainShortcutInfo, mainOptions, sidePosition, snapPosition, adapter, instanceId); @@ -942,7 +943,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId, @Nullable Bundle mainOptions, @SplitPosition int sidePosition, - @SnapPosition int snapPosition, RemoteAnimationAdapter adapter, + @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) { startWithLegacyTransition(wct, mainTaskId, null /* mainPendingIntent */, null /* mainFillInIntent */, null /* mainShortcutInfo */, mainOptions, sidePosition, @@ -957,7 +958,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId, @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent, @Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle options, - @SplitPosition int sidePosition, @SnapPosition int snapPosition, + @SplitPosition int sidePosition, @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) { if (!isSplitScreenVisible()) { exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java index a68b41d6563a..3e06d2d0e797 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java @@ -19,7 +19,7 @@ import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; -import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition; +import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition; import java.util.Objects; @@ -39,7 +39,7 @@ public class SplitBounds implements Parcelable { public final float leftTaskPercent; public final float dividerWidthPercent; public final float dividerHeightPercent; - public final @SnapPosition int snapPosition; + public final @PersistentSnapPosition int snapPosition; /** * If {@code true}, that means at the time of creation of this object, the * split-screened apps were vertically stacked. This is useful in scenarios like @@ -51,7 +51,7 @@ public class SplitBounds implements Parcelable { public final int rightBottomTaskId; public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds, int leftTopTaskId, - int rightBottomTaskId, @SnapPosition int snapPosition) { + int rightBottomTaskId, @PersistentSnapPosition int snapPosition) { this.leftTopBounds = leftTopBounds; this.rightBottomBounds = rightBottomBounds; this.leftTopTaskId = leftTopTaskId; 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 afa2754803f1..780bbb5c9f31 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 @@ -79,11 +79,13 @@ import com.android.wm.shell.recents.RecentsTransitionStateListener; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.KeyguardChangeListener; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener; +import java.io.PrintWriter; import java.util.Optional; import java.util.function.Supplier; @@ -97,6 +99,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory; private final ActivityTaskManager mActivityTaskManager; + private final ShellCommandHandler mShellCommandHandler; private final ShellTaskOrganizer mTaskOrganizer; private final ShellController mShellController; private final Context mContext; @@ -134,6 +137,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { Handler mainHandler, Choreographer mainChoreographer, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ShellTaskOrganizer taskOrganizer, DisplayController displayController, ShellController shellController, @@ -148,6 +152,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mainHandler, mainChoreographer, shellInit, + shellCommandHandler, taskOrganizer, displayController, shellController, @@ -167,6 +172,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { Handler mainHandler, Choreographer mainChoreographer, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ShellTaskOrganizer taskOrganizer, DisplayController displayController, ShellController shellController, @@ -189,7 +195,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mTransitions = transitions; mDesktopTasksController = desktopTasksController; mRecentsTransitionHandler = recentsTransitionHandler; - + mShellCommandHandler = shellCommandHandler; mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory; mInputMonitorFactory = inputMonitorFactory; mTransactionFactory = transactionFactory; @@ -206,6 +212,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { onRecentsTransitionStarted(transition); } }); + mShellCommandHandler.addDumpCallback(this::dump, this); } @Override @@ -593,6 +600,15 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { super.dispose(); } + @Override + public String toString() { + return "EventReceiver" + + "{" + + "tasksOnDisplay=" + + mTasksOnDisplay + + "}"; + } + private void incrementTaskNumber() { mTasksOnDisplay++; } @@ -981,6 +997,15 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { && mSplitScreenController.isTaskInSplitScreen(taskId); } + private void dump(PrintWriter pw, String prefix) { + final String innerPrefix = prefix + " "; + pw.println(prefix + "DesktopModeWindowDecorViewModel"); + pw.println(innerPrefix + "DesktopModeStatus=" + DesktopModeStatus.isEnabled()); + pw.println(innerPrefix + "mTransitionDragActive=" + mTransitionDragActive); + pw.println(innerPrefix + "mEventReceiversByDisplay=" + mEventReceiversByDisplay); + pw.println(innerPrefix + "mWindowDecorByTaskId=" + mWindowDecorByTaskId); + } + private class DragStartListenerImpl implements DragPositioningCallbackUtility.DragStartListener { @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 84ec6b389c4a..380b59e84485 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -18,6 +18,7 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.windowingModeToString; import android.app.ActivityManager; import android.app.WindowConfiguration.WindowingMode; @@ -661,6 +662,17 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mRelayoutBlock++; } + @Override + public String toString() { + return "{" + + "mPositionInParent=" + mPositionInParent + ", " + + "mRelayoutBlock=" + mRelayoutBlock + ", " + + "taskId=" + mTaskInfo.taskId + ", " + + "windowingMode=" + windowingModeToString(mTaskInfo.getWindowingMode()) + ", " + + "isFocused=" + isFocused() + + "}"; + } + static class Factory { DesktopModeWindowDecoration create( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenConstantsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenConstantsTest.kt new file mode 100644 index 000000000000..fe261107d65b --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenConstantsTest.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.common.split + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SplitScreenConstantsTest { + + /** + * Ensures that some important constants are not changed from their set values. These values + * are persisted in user-defined app pairs, and changing them will break things. + */ + @Test + fun shouldKeepExistingConstantValues() { + assertEquals( + "the value of SPLIT_POSITION_TOP_OR_LEFT should be 0", + 0, + SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT, + ) + assertEquals( + "the value of SPLIT_POSITION_BOTTOM_OR_RIGHT should be 1", + 1, + SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT, + ) + assertEquals( + "the value of SNAP_TO_30_70 should be 0", + 0, + SplitScreenConstants.SNAP_TO_30_70, + ) + assertEquals( + "the value of SNAP_TO_50_50 should be 1", + 1, + SplitScreenConstants.SNAP_TO_50_50, + ) + assertEquals( + "the value of SNAP_TO_70_30 should be 2", + 2, + SplitScreenConstants.SNAP_TO_70_30, + ) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 00d70a75837b..8eaf5a004c0a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -49,6 +49,7 @@ import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener import com.android.wm.shell.sysui.KeyguardChangeListener +import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions @@ -89,6 +90,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Mock private lateinit var mockShellExecutor: ShellExecutor @Mock private lateinit var mockRootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer @Mock private lateinit var mockRecentsTransitionHandler: RecentsTransitionHandler + @Mock private lateinit var mockShellCommandHandler: ShellCommandHandler private val transactionFactory = Supplier<SurfaceControl.Transaction> { SurfaceControl.Transaction() @@ -105,6 +107,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { mockMainHandler, mockMainChoreographer, shellInit, + mockShellCommandHandler, mockTaskOrganizer, mockDisplayController, mockShellController, diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp index 2c13ceb77b52..a952be020855 100644 --- a/libs/hwui/jni/Shader.cpp +++ b/libs/hwui/jni/Shader.cpp @@ -65,21 +65,41 @@ static jlong Shader_getNativeFinalizer(JNIEnv*, jobject) { return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Shader_safeUnref)); } -static jlong createBitmapShaderHelper(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle, - jint tileModeX, jint tileModeY, bool isDirectSampled, - const SkSamplingOptions& sampling) { +/////////////////////////////////////////////////////////////////////////////////////////////// + +static SkGainmapInfo sNoOpGainmap = { + .fGainmapRatioMin = {1.f, 1.f, 1.f, 1.0}, + .fGainmapRatioMax = {1.f, 1.f, 1.f, 1.0}, + .fGainmapGamma = {1.f, 1.f, 1.f, 1.f}, + .fEpsilonSdr = {0.f, 0.f, 0.f, 1.0}, + .fEpsilonHdr = {0.f, 0.f, 0.f, 1.0}, + .fDisplayRatioSdr = 1.f, + .fDisplayRatioHdr = 1.f, +}; + +static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle, + jint tileModeX, jint tileModeY, jint maxAniso, bool filter, + bool isDirectSampled, jlong overrideGainmapPtr) { + SkSamplingOptions sampling = maxAniso > 0 ? SkSamplingOptions::Aniso(static_cast<int>(maxAniso)) + : SkSamplingOptions(filter ? SkFilterMode::kLinear + : SkFilterMode::kNearest, + SkMipmapMode::kNone); const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr); + const Gainmap* gainmap = reinterpret_cast<Gainmap*>(overrideGainmapPtr); sk_sp<SkImage> image; if (bitmapHandle) { // Only pass a valid SkBitmap object to the constructor if the Bitmap exists. Otherwise, // we'll pass an empty SkBitmap to avoid crashing/excepting for compatibility. auto& bitmap = android::bitmap::toBitmap(bitmapHandle); image = bitmap.makeImage(); + if (!gainmap && bitmap.hasGainmap()) { + gainmap = bitmap.gainmap().get(); + } - if (!isDirectSampled && bitmap.hasGainmap()) { - sk_sp<SkShader> gainmapShader = MakeGainmapShader( - image, bitmap.gainmap()->bitmap->makeImage(), bitmap.gainmap()->info, - (SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling); + if (!isDirectSampled && gainmap && gainmap->info != sNoOpGainmap) { + sk_sp<SkShader> gainmapShader = + MakeGainmapShader(image, gainmap->bitmap->makeImage(), gainmap->info, + (SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling); if (gainmapShader) { if (matrix) { gainmapShader = gainmapShader->makeWithLocalMatrix(*matrix); @@ -111,26 +131,6 @@ static jlong createBitmapShaderHelper(JNIEnv* env, jobject o, jlong matrixPtr, j /////////////////////////////////////////////////////////////////////////////////////////////// -static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle, - jint tileModeX, jint tileModeY, bool filter, - bool isDirectSampled) { - SkSamplingOptions sampling(filter ? SkFilterMode::kLinear : SkFilterMode::kNearest, - SkMipmapMode::kNone); - return createBitmapShaderHelper(env, o, matrixPtr, bitmapHandle, tileModeX, tileModeY, - isDirectSampled, sampling); -} - -static jlong BitmapShader_constructorWithMaxAniso(JNIEnv* env, jobject o, jlong matrixPtr, - jlong bitmapHandle, jint tileModeX, - jint tileModeY, jint maxAniso, - bool isDirectSampled) { - auto sampling = SkSamplingOptions::Aniso(static_cast<int>(maxAniso)); - return createBitmapShaderHelper(env, o, matrixPtr, bitmapHandle, tileModeX, tileModeY, - isDirectSampled, sampling); -} - -/////////////////////////////////////////////////////////////////////////////////////////////// - static std::vector<SkColor4f> convertColorLongs(JNIEnv* env, jlongArray colorArray) { const size_t count = env->GetArrayLength(colorArray); const jlong* colorValues = env->GetLongArrayElements(colorArray, nullptr); @@ -419,8 +419,7 @@ static const JNINativeMethod gShaderMethods[] = { }; static const JNINativeMethod gBitmapShaderMethods[] = { - {"nativeCreate", "(JJIIZZ)J", (void*)BitmapShader_constructor}, - {"nativeCreateWithMaxAniso", "(JJIIIZ)J", (void*)BitmapShader_constructorWithMaxAniso}, + {"nativeCreate", "(JJIIIZZJ)J", (void*)BitmapShader_constructor}, }; diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt index 8986e5237f61..98ad22c8060e 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt @@ -29,7 +29,7 @@ fun Intent.parse( packageManager: PackageManager, previousIntent: Intent? = null, ): Request { - this.toRequestClose(packageManager, previousIntent)?.let { closeRequest -> + this.toRequestClose(previousIntent)?.let { closeRequest -> return closeRequest } diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/PasswordKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/PasswordKtx.kt new file mode 100644 index 000000000000..34710704facb --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/PasswordKtx.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0N + * + * 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.ktx + +import androidx.activity.result.IntentSenderRequest +import com.android.credentialmanager.IS_AUTO_SELECTED_KEY +import com.android.credentialmanager.model.Password + +fun Password.getIntentSenderRequest( + isAutoSelected: Boolean = false +): IntentSenderRequest { + val entryIntent = entry.frameworkExtrasIntent + entryIntent?.putExtra(IS_AUTO_SELECTED_KEY, isAutoSelected) + + return IntentSenderRequest.Builder( + pendingIntent = passwordCredentialEntry.pendingIntent + ).setFillInIntent(entryIntent).build() +} diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCancelMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCancelMapper.kt index 555a86f59377..99dc9ec38eff 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCancelMapper.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCancelMapper.kt @@ -31,9 +31,6 @@ fun Intent.toRequestCancel(packageManager: PackageManager): Request.Cancel? = Log.d(TAG, "Received UI cancel request with an invalid package name.") null } else { - Request.Cancel( - showCancellationUi = cancelUiRequest.shouldShowCancellationUi(), - appName = appLabel - ) + Request.Cancel(appName = appLabel) } } diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCloseMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCloseMapper.kt index 6de3e7d60bf8..02ee77bc5ec3 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCloseMapper.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCloseMapper.kt @@ -17,31 +17,33 @@ package com.android.credentialmanager.mapper import android.content.Intent -import android.content.pm.PackageManager +import com.android.credentialmanager.ktx.cancelUiRequest import com.android.credentialmanager.ktx.requestInfo import com.android.credentialmanager.model.Request fun Intent.toRequestClose( - packageManager: PackageManager, previousIntent: Intent? = null, ): Request.Close? { // Close request comes as "Cancel" request from Credential Manager API - val currentRequest = toRequestCancel(packageManager = packageManager) ?: return null + this.cancelUiRequest?.let { cancelUiRequest -> - if (currentRequest.showCancellationUi) { - // Current request is to Cancel and not to Close - return null - } + if (cancelUiRequest.shouldShowCancellationUi()) { + // Current request is to Cancel and not to Close + return null + } - previousIntent?.let { - val previousToken = previousIntent.requestInfo?.token - val currentToken = this.requestInfo?.token + previousIntent?.let { + val previousToken = previousIntent.requestInfo?.token + val currentToken = this.requestInfo?.token - if (previousToken != currentToken) { - // Current cancellation is for a different request, don't close the current flow. - return null + if (previousToken != currentToken) { + // Current cancellation is for a different request, don't close the current flow. + return null + } } + + return Request.Close } - return Request.Close + return null }
\ No newline at end of file diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt index 6011a1c1933a..ed98f3ed5154 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt @@ -37,7 +37,6 @@ sealed class Request { * Request to close the app, displaying a message to the user. */ data class Cancel( - val showCancellationUi: Boolean, val appName: String ) : Request() diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/PasswordRepository.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/PasswordRepository.kt new file mode 100644 index 000000000000..1cce3baa303e --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/PasswordRepository.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0N + * + * 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.repository + +import android.content.Intent +import android.credentials.ui.BaseDialogResult +import android.credentials.ui.ProviderPendingIntentResponse +import android.credentials.ui.UserSelectionDialogResult +import android.os.Bundle +import android.util.Log +import com.android.credentialmanager.TAG +import com.android.credentialmanager.model.Password +import com.android.credentialmanager.model.Request + +class PasswordRepository { + + suspend fun selectPassword( + password: Password, + request: Request.Get, + resultCode: Int? = null, + resultData: Intent? = null, + ) { + Log.d(TAG, "password selected: {provider=${password.providerId}" + + ", key=${password.entry.key}, subkey=${password.entry.subkey}}") + + val userSelectionDialogResult = UserSelectionDialogResult( + request.token, + password.providerId, + password.entry.key, + password.entry.subkey, + if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null + ) + val resultDataBundle = Bundle() + UserSelectionDialogResult.addToBundle(userSelectionDialogResult, resultDataBundle) + request.resultReceiver?.send( + BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION, + resultDataBundle + ) + } +} diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt index 7c81fd06c4e2..e8e403301129 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt @@ -18,11 +18,13 @@ package com.android.credentialmanager import android.app.Application import com.android.credentialmanager.di.inject +import com.android.credentialmanager.repository.PasswordRepository import com.android.credentialmanager.repository.RequestRepository class CredentialSelectorApp : Application() { lateinit var requestRepository: RequestRepository + lateinit var passwordRepository: PasswordRepository override fun onCreate() { super.onCreate() diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/di/DI.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/di/DI.kt index a11017b919bf..1e8f83d69e42 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/di/DI.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/di/DI.kt @@ -2,12 +2,14 @@ package com.android.credentialmanager.di import android.app.Application import com.android.credentialmanager.CredentialSelectorApp +import com.android.credentialmanager.repository.PasswordRepository import com.android.credentialmanager.repository.RequestRepository // TODO b/301601582 add Hilt for dependency injection fun CredentialSelectorApp.inject() { requestRepository = requestRepository(application = this) + passwordRepository = passwordRepository() } private fun requestRepository( @@ -15,3 +17,5 @@ private fun requestRepository( ): RequestRepository = RequestRepository( application = application, ) + +private fun passwordRepository(): PasswordRepository = PasswordRepository() diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt index c885ec47024a..c87cfd374cd3 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt @@ -35,6 +35,7 @@ import com.android.credentialmanager.activity.StartBalIntentSenderForResultContr import com.android.credentialmanager.ui.components.DialogButtonsRow import com.android.credentialmanager.ui.components.PasswordRow import com.android.credentialmanager.ui.components.SignInHeader +import com.android.credentialmanager.ui.model.PasswordUiModel import com.android.credentialmanager.ui.screens.single.SingleAccountScreen import com.google.android.horologist.annotations.ExperimentalHorologistApi import com.google.android.horologist.compose.layout.ScalingLazyColumnState @@ -59,9 +60,8 @@ fun SinglePasswordScreen( } is SinglePasswordScreenUiState.Loaded -> { - val model = state.passwordUiModel SinglePasswordScreen( - email = model.email, + passwordUiModel = state.passwordUiModel, onCancelClick = viewModel::onCancelClick, onOKClick = viewModel::onOKClick, columnState = columnState, @@ -98,7 +98,7 @@ fun SinglePasswordScreen( @Composable fun SinglePasswordScreen( - email: String, + passwordUiModel: PasswordUiModel, onCancelClick: () -> Unit, onOKClick: () -> Unit, columnState: ScalingLazyColumnState, @@ -113,7 +113,7 @@ fun SinglePasswordScreen( }, accountContent = { PasswordRow( - email = email, + email = passwordUiModel.email, modifier = Modifier.padding(top = 10.dp), ) }, @@ -134,7 +134,7 @@ fun SinglePasswordScreen( @Composable fun SinglePasswordScreenPreview() { SinglePasswordScreen( - email = "beckett_bakery@gmail.com", + passwordUiModel = PasswordUiModel(email = "beckett_bakery@gmail.com"), onCancelClick = {}, onOKClick = {}, columnState = belowTimeTextPreview(), diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt index 9b0662257ef6..3167e6784e66 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt @@ -17,10 +17,6 @@ package com.android.credentialmanager.ui.screens.single.password import android.content.Intent -import android.credentials.ui.BaseDialogResult -import android.credentials.ui.ProviderPendingIntentResponse -import android.credentials.ui.UserSelectionDialogResult -import android.os.Bundle import android.util.Log import androidx.activity.result.IntentSenderRequest import androidx.annotation.MainThread @@ -30,10 +26,11 @@ import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.AP import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.CreationExtras import com.android.credentialmanager.CredentialSelectorApp -import com.android.credentialmanager.IS_AUTO_SELECTED_KEY import com.android.credentialmanager.TAG +import com.android.credentialmanager.ktx.getIntentSenderRequest import com.android.credentialmanager.model.Password import com.android.credentialmanager.model.Request +import com.android.credentialmanager.repository.PasswordRepository import com.android.credentialmanager.repository.RequestRepository import com.android.credentialmanager.ui.model.PasswordUiModel import kotlinx.coroutines.flow.MutableStateFlow @@ -43,6 +40,7 @@ import kotlinx.coroutines.launch class SinglePasswordScreenViewModel( private val requestRepository: RequestRepository, + private val passwordRepository: PasswordRepository, ) : ViewModel() { private var initializeCalled = false @@ -87,15 +85,8 @@ class SinglePasswordScreenViewModel( } fun onOKClick() { - // TODO: b/301206470 move this code to shared module - val entryIntent = password.entry.frameworkExtrasIntent - entryIntent?.putExtra(IS_AUTO_SELECTED_KEY, false) - val intentSenderRequest = IntentSenderRequest.Builder( - pendingIntent = password.passwordCredentialEntry.pendingIntent - ).setFillInIntent(entryIntent).build() - _uiState.value = SinglePasswordScreenUiState.PasswordSelected( - intentSenderRequest = intentSenderRequest + intentSenderRequest = password.getIntentSenderRequest() ) } @@ -103,25 +94,16 @@ class SinglePasswordScreenViewModel( resultCode: Int? = null, resultData: Intent? = null, ) { - // TODO: b/301206470 move this code to shared module - Log.d(TAG, "credential selected: {provider=${password.providerId}" + - ", key=${password.entry.key}, subkey=${password.entry.subkey}}") - - val userSelectionDialogResult = UserSelectionDialogResult( - requestGet.token, - password.providerId, - password.entry.key, - password.entry.subkey, - if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null - ) - val resultDataBundle = Bundle() - UserSelectionDialogResult.addToBundle(userSelectionDialogResult, resultDataBundle) - requestGet.resultReceiver?.send( - BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION, - resultDataBundle - ) - - _uiState.value = SinglePasswordScreenUiState.Completed + viewModelScope.launch { + passwordRepository.selectPassword( + password = password, + request = requestGet, + resultCode = resultCode, + resultData = resultData + ) + + _uiState.value = SinglePasswordScreenUiState.Completed + } } companion object { @@ -135,6 +117,7 @@ class SinglePasswordScreenViewModel( return SinglePasswordScreenViewModel( requestRepository = (application as CredentialSelectorApp).requestRepository, + passwordRepository = application.passwordRepository, ) as T } } diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 70832f594941..437f8afa8d70 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -5,4 +5,11 @@ flag { namespace: "systemui" description: "An Example Flag" bug: "292511372" -}
\ No newline at end of file +} + +flag { + name: "sysui_teamfood" + namespace: "systemui" + description: "Enables all the sysui classic flags that are marked as being in teamfood" + bug: "302578396" +} diff --git a/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt b/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt index 44c41058f99d..bb2fbf73b2e5 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt @@ -26,6 +26,7 @@ import androidx.compose.material3.ButtonColors import androidx.compose.material3.ButtonDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.android.compose.theme.LocalAndroidColorScheme @@ -34,11 +35,13 @@ fun PlatformButton( onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, + colors: ButtonColors = filledButtonColors(), + verticalPadding: Dp = DefaultPlatformButtonVerticalPadding, content: @Composable RowScope.() -> Unit, ) { androidx.compose.material3.Button( - modifier = modifier.padding(vertical = 6.dp).height(36.dp), - colors = filledButtonColors(), + modifier = modifier.padding(vertical = verticalPadding).height(36.dp), + colors = colors, contentPadding = ButtonPaddings, onClick = onClick, enabled = enabled, @@ -52,13 +55,16 @@ fun PlatformOutlinedButton( onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, + colors: ButtonColors = outlineButtonColors(), + border: BorderStroke? = outlineButtonBorder(), + verticalPadding: Dp = DefaultPlatformButtonVerticalPadding, content: @Composable RowScope.() -> Unit, ) { androidx.compose.material3.OutlinedButton( - modifier = modifier.padding(vertical = 6.dp).height(36.dp), + modifier = modifier.padding(vertical = verticalPadding).height(36.dp), enabled = enabled, - colors = outlineButtonColors(), - border = outlineButtonBorder(), + colors = colors, + border = border, contentPadding = ButtonPaddings, onClick = onClick, ) { @@ -71,6 +77,7 @@ fun PlatformTextButton( onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, + colors: ButtonColors = textButtonColors(), content: @Composable RowScope.() -> Unit, ) { androidx.compose.material3.TextButton( @@ -78,10 +85,11 @@ fun PlatformTextButton( modifier = modifier, enabled = enabled, content = content, - colors = textButtonColors(), + colors = colors, ) } +private val DefaultPlatformButtonVerticalPadding = 6.dp private val ButtonPaddings = PaddingValues(horizontal = 16.dp, vertical = 8.dp) @Composable diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt index a61e95931222..a9944f739975 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt @@ -24,18 +24,30 @@ import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.snap import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas +import androidx.compose.foundation.Image import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass @@ -48,12 +60,18 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.times +import com.android.compose.PlatformButton import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.SceneScope import com.android.compose.windowsizeclass.LocalWindowSizeClass @@ -62,6 +80,8 @@ import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel +import com.android.systemui.common.shared.model.Text.Companion.loadText +import com.android.systemui.common.ui.compose.Icon import com.android.systemui.dagger.SysUISingleton import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Direction @@ -70,6 +90,9 @@ import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.scene.ui.composable.ComposableScene import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.pow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -164,7 +187,7 @@ private fun Bouncer( Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(60.dp), - modifier = modifier.padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 32.dp) + modifier = modifier.padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 92.dp) ) { Crossfade( targetState = message, @@ -201,18 +224,20 @@ private fun Bouncer( } } - Button( - onClick = viewModel::onEmergencyServicesButtonClicked, - colors = - ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.tertiaryContainer, - contentColor = MaterialTheme.colorScheme.onTertiaryContainer, - ), - ) { - Text( - text = stringResource(com.android.internal.R.string.lockscreen_emergency_call), - style = MaterialTheme.typography.bodyMedium, - ) + if (viewModel.isEmergencyButtonVisible) { + Button( + onClick = viewModel::onEmergencyServicesButtonClicked, + colors = + ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.tertiaryContainer, + contentColor = MaterialTheme.colorScheme.onTertiaryContainer, + ), + ) { + Text( + text = stringResource(com.android.internal.R.string.lockscreen_emergency_call), + style = MaterialTheme.typography.bodyMedium, + ) + } } if (dialogMessage != null) { @@ -241,16 +266,133 @@ private fun Bouncer( /** Renders the UI of the user switcher that's displayed on large screens next to the bouncer UI. */ @Composable private fun UserSwitcher( + viewModel: BouncerViewModel, modifier: Modifier = Modifier, ) { - Box(modifier) { - Text( - text = "TODO: the user switcher goes here", - modifier = Modifier.align(Alignment.Center) + val selectedUserImage by viewModel.selectedUserImage.collectAsState(null) + val dropdownItems by viewModel.userSwitcherDropdown.collectAsState(emptyList()) + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = modifier, + ) { + selectedUserImage?.let { + Image( + bitmap = it.asImageBitmap(), + contentDescription = null, + modifier = Modifier.size(SelectedUserImageSize), + ) + } + + UserSwitcherDropdown( + items = dropdownItems, ) } } +@Composable +private fun UserSwitcherDropdown( + items: List<BouncerViewModel.UserSwitcherDropdownItemViewModel>, +) { + val (isDropdownExpanded, setDropdownExpanded) = remember { mutableStateOf(false) } + + items.firstOrNull()?.let { firstDropdownItem -> + Spacer(modifier = Modifier.height(40.dp)) + + Box { + PlatformButton( + modifier = + Modifier + // Remove the built-in padding applied inside PlatformButton: + .padding(vertical = 0.dp) + .width(UserSwitcherDropdownWidth) + .height(UserSwitcherDropdownHeight), + colors = + ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.surfaceContainerHighest, + contentColor = MaterialTheme.colorScheme.onSurface, + ), + onClick = { setDropdownExpanded(!isDropdownExpanded) }, + ) { + val context = LocalContext.current + Text( + text = checkNotNull(firstDropdownItem.text.loadText(context)), + style = MaterialTheme.typography.headlineSmall, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + + Spacer(modifier = Modifier.weight(1f)) + + Icon( + imageVector = Icons.Default.KeyboardArrowDown, + contentDescription = null, + modifier = Modifier.size(32.dp), + ) + } + + UserSwitcherDropdownMenu( + isExpanded = isDropdownExpanded, + items = items, + onDismissed = { setDropdownExpanded(false) }, + ) + } + } +} + +@Composable +private fun UserSwitcherDropdownMenu( + isExpanded: Boolean, + items: List<BouncerViewModel.UserSwitcherDropdownItemViewModel>, + onDismissed: () -> Unit, +) { + val context = LocalContext.current + + // TODO(b/303071855): once the FR is fixed, remove this composition local override. + MaterialTheme( + colorScheme = + MaterialTheme.colorScheme.copy( + surface = MaterialTheme.colorScheme.surfaceContainerHighest, + ), + shapes = MaterialTheme.shapes.copy(extraSmall = RoundedCornerShape(28.dp)), + ) { + DropdownMenu( + expanded = isExpanded, + onDismissRequest = onDismissed, + offset = + DpOffset( + x = 0.dp, + y = -UserSwitcherDropdownHeight, + ), + modifier = Modifier.width(UserSwitcherDropdownWidth), + ) { + items.forEach { userSwitcherDropdownItem -> + DropdownMenuItem( + leadingIcon = { + Icon( + icon = userSwitcherDropdownItem.icon, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(28.dp), + ) + }, + text = { + Text( + text = checkNotNull(userSwitcherDropdownItem.text.loadText(context)), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface, + ) + }, + onClick = { + onDismissed() + userSwitcherDropdownItem.onClick() + }, + ) + } + } + } +} + /** * Arranges the bouncer contents and user switcher contents side-by-side, supporting a double tap * anywhere on the background to flip their positions. @@ -293,7 +435,7 @@ private fun SideBySide( 1f } else { // Since the user switcher is not first, the elements have to be swapped - // horizontally. In the case of RTL locales, this means pushing the user + // horizontally. In the case of RTL locale, this means pushing the user // switcher to the left, hence the negative number. -1f }, @@ -301,21 +443,28 @@ private fun SideBySide( ) UserSwitcher( + viewModel = viewModel, modifier = Modifier.fillMaxHeight().weight(1f).graphicsLayer { translationX = size.width * animatedOffset + alpha = animatedAlpha(animatedOffset) }, ) - Bouncer( - viewModel = viewModel, - dialogFactory = dialogFactory, + Box( modifier = Modifier.fillMaxHeight().weight(1f).graphicsLayer { // A negative sign is used to make sure this is offset in the direction that's // opposite of the direction that the user switcher is pushed in. translationX = -size.width * animatedOffset - }, - ) + alpha = animatedAlpha(animatedOffset) + } + ) { + Bouncer( + viewModel = viewModel, + dialogFactory = dialogFactory, + modifier = Modifier.widthIn(max = 400.dp).align(Alignment.BottomCenter), + ) + } } } @@ -330,6 +479,7 @@ private fun Stacked( modifier = modifier, ) { UserSwitcher( + viewModel = viewModel, modifier = Modifier.fillMaxWidth().weight(1f), ) Bouncer( @@ -343,3 +493,36 @@ private fun Stacked( interface BouncerSceneDialogFactory { operator fun invoke(): AlertDialog } + +/** + * Calculates an alpha for the user switcher and bouncer such that it's at `1` when the offset of + * the two reaches a stopping point but `0` in the middle of the transition. + */ +private fun animatedAlpha( + offset: Float, +): Float { + // Describes a curve that is made of two parabolic U-shaped curves mirrored horizontally around + // the y-axis. The U on the left runs between x = -1 and x = 0 while the U on the right runs + // between x = 0 and x = 1. + // + // The minimum values of the curves are at -0.5 and +0.5. + // + // Both U curves are vertically scaled such that they reach the points (-1, 1) and (1, 1). + // + // Breaking it down, it's y = a×(|x|-m)²+b, where: + // x: the offset + // y: the alpha + // m: x-axis center of the parabolic curves, where the minima are. + // b: y-axis offset to apply to the entire curve so the animation spends more time with alpha = + // 0. + // a: amplitude to scale the parabolic curves to reach y = 1 at x = -1, x = 0, and x = +1. + val m = 0.5f + val b = -0.25 + val a = (1 - b) / m.pow(2) + + return max(0f, (a * (abs(offset) - m).pow(2) + b).toFloat()) +} + +private val SelectedUserImageSize = 190.dp +private val UserSwitcherDropdownWidth = SelectedUserImageSize + 2 * 29.dp +private val UserSwitcherDropdownHeight = 60.dp diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt index cb76ad7c77fe..651594c2d8f9 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -509,7 +509,6 @@ open class ClockRegistry( private var isQueued = AtomicBoolean(false) fun verifyLoadedProviders() { - Log.i(TAG, Thread.currentThread().getStackTrace().toString()) val shouldSchedule = isQueued.compareAndSet(false, true) if (!shouldSchedule) { logger.tryLog( diff --git a/packages/SystemUI/res/drawable/bluetooth_tile_dialog_bg_off.xml b/packages/SystemUI/res/drawable/bluetooth_tile_dialog_bg_off.xml new file mode 100644 index 000000000000..d4916b47158c --- /dev/null +++ b/packages/SystemUI/res/drawable/bluetooth_tile_dialog_bg_off.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="?android:attr/colorControlHighlight"> + <item android:id="@android:id/mask"> + <shape android:shape="rectangle"> + <solid android:color="@android:color/white"/> + <corners android:radius="@dimen/settingslib_switch_bar_radius"/> + </shape> + </item> +</ripple>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/bluetooth_tile_dialog_bg_off_busy.xml b/packages/SystemUI/res/drawable/bluetooth_tile_dialog_bg_off_busy.xml new file mode 100644 index 000000000000..7bc120ee8aad --- /dev/null +++ b/packages/SystemUI/res/drawable/bluetooth_tile_dialog_bg_off_busy.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@android:id/background"> + <shape> + <solid android:color="?android:attr/colorControlHighlight" /> + <corners android:radius="@dimen/settingslib_switch_bar_radius"/> + </shape> + </item> +</layer-list>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/bluetooth_device_item.xml b/packages/SystemUI/res/layout/bluetooth_device_item.xml index 6dd44fbe645d..1c7e9977afe5 100644 --- a/packages/SystemUI/res/layout/bluetooth_device_item.xml +++ b/packages/SystemUI/res/layout/bluetooth_device_item.xml @@ -14,81 +14,74 @@ ~ limitations under the License. --> -<!-- TODO(b/298124674) remove this root --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" - android:id="@+id/bluetooth_device_list_container" + android:id="@+id/bluetooth_device_row" + style="@style/BluetoothTileDialog.Device" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" + android:layout_height="@dimen/bluetooth_dialog_device_height" + android:paddingEnd="24dp" + android:paddingStart="20dp" android:layout_marginBottom="4dp"> - <androidx.constraintlayout.widget.ConstraintLayout - android:id="@+id/bluetooth_device_row" - style="@style/BluetoothTileDialog.Device" - android:layout_height="@dimen/bluetooth_dialog_device_height" - android:paddingEnd="24dp" - android:paddingStart="20dp" - android:baselineAligned="false"> - - <ImageView - android:id="@+id/bluetooth_device_icon" - android:contentDescription="@string/accessibility_bluetooth_device_icon" - android:layout_width="24dp" - android:layout_height="24dp" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toBottomOf="parent" - android:layout_gravity="center_vertical" /> + <ImageView + android:id="@+id/bluetooth_device_icon" + android:contentDescription="@string/accessibility_bluetooth_device_icon" + android:layout_width="24dp" + android:layout_height="24dp" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + android:layout_gravity="center_vertical" /> - <View - android:id="@+id/bluetooth_device" - android:layout_width="0dp" - android:layout_height="0dp" - app:layout_constraintTop_toTopOf="@+id/bluetooth_device_name" - app:layout_constraintBottom_toBottomOf="@+id/bluetooth_device_summary" - app:layout_constraintStart_toStartOf="@+id/bluetooth_device_name" - app:layout_constraintEnd_toEndOf="@+id/bluetooth_device_name" /> + <TextView + android:layout_width="0dp" + android:id="@+id/bluetooth_device_name" + style="@style/BluetoothTileDialog.DeviceName" + android:paddingStart="20dp" + android:paddingTop="10dp" + app:layout_constraintWidth_percent="0.7" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon" + app:layout_constraintEnd_toStartOf="@+id/gear_icon" + app:layout_constraintBottom_toTopOf="@+id/bluetooth_device_summary" + android:gravity="center_vertical" + android:textSize="14sp" /> - <TextView - android:layout_width="0dp" - android:id="@+id/bluetooth_device_name" - style="@style/BluetoothTileDialog.DeviceName" - android:paddingStart="20dp" - android:paddingTop="10dp" - app:layout_constraintWidth_percent="0.7" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon" - app:layout_constraintEnd_toStartOf="@+id/gear_icon" - app:layout_constraintBottom_toTopOf="@+id/bluetooth_device_summary" - android:gravity="center_vertical" - android:textSize="14sp" /> + <TextView + android:layout_width="0dp" + android:id="@+id/bluetooth_device_summary" + style="@style/BluetoothTileDialog.DeviceSummary" + android:paddingStart="20dp" + android:paddingBottom="10dp" + app:layout_constraintWidth_percent="0.7" + app:layout_constraintTop_toBottomOf="@+id/bluetooth_device_name" + app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon" + app:layout_constraintEnd_toStartOf="@+id/gear_icon" + app:layout_constraintBottom_toBottomOf="parent" + android:gravity="center_vertical" /> - <TextView - android:layout_width="0dp" - android:id="@+id/bluetooth_device_summary" - style="@style/BluetoothTileDialog.DeviceSummary" - android:paddingStart="20dp" - android:paddingBottom="10dp" - app:layout_constraintWidth_percent="0.7" - app:layout_constraintTop_toBottomOf="@+id/bluetooth_device_name" - app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon" - app:layout_constraintEnd_toStartOf="@+id/gear_icon" - app:layout_constraintBottom_toBottomOf="parent" - android:gravity="center_vertical" /> + <View + android:id="@+id/gear_icon" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintStart_toEndOf="@+id/bluetooth_device_name" + app:layout_constraintEnd_toEndOf="@+id/gear_icon_image" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" /> - <ImageView - android:id="@+id/gear_icon" - android:src="@drawable/ic_settings_24dp" - android:contentDescription="@string/accessibility_bluetooth_device_settings_gear" - android:layout_width="0dp" - android:layout_height="24dp" - app:layout_constraintStart_toEndOf="@+id/bluetooth_device_name" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintWidth_percent="0.3" - android:gravity="center_vertical" - android:paddingStart="10dp" /> - </androidx.constraintlayout.widget.ConstraintLayout> -</LinearLayout>
\ No newline at end of file + <ImageView + android:id="@+id/gear_icon_image" + android:src="@drawable/ic_settings_24dp" + android:contentDescription="@string/accessibility_bluetooth_device_settings_gear" + android:layout_width="0dp" + android:layout_height="24dp" + app:layout_constraintStart_toEndOf="@+id/bluetooth_device_name" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintWidth_percent="0.3" + android:gravity="center_vertical" + android:paddingStart="10dp" /> +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 6cdd15e637bd..81101d828c86 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -221,6 +221,7 @@ <item type="id" name="split_shade_guideline" /> <item type="id" name="lock_icon" /> <item type="id" name="lock_icon_bg" /> + <item type="id" name="burn_in_layer" /> <!-- Privacy dialog --> <item type="id" name="privacy_dialog_close_app_button" /> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 4b23795ae90c..67b705222977 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -552,6 +552,10 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV constraintSet.applyTo(layout); } + public ClockController getClockController() { + return mKeyguardClockSwitchController.getClock(); + } + @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { mView.dump(pw, args); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index c09e68d4ace6..f94f8c594aa6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -328,7 +328,10 @@ class AuthRippleController @Inject constructor( private val udfpsControllerCallback = object : UdfpsController.Callback { override fun onFingerDown() { - showDwellRipple() + // only show dwell ripple for device entry + if (keyguardUpdateMonitor.isFingerprintDetectionRunning) { + showDwellRipple() + } } override fun onFingerUp() { diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt index 0cbfb68b6e93..7f3b794e2ac3 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt @@ -16,10 +16,16 @@ package com.android.systemui.bouncer.ui +import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModelModule import dagger.Binds import dagger.Module -@Module +@Module( + includes = + [ + BouncerViewModelModule::class, + ], +) interface BouncerViewModule { /** Binds BouncerView to BouncerViewImpl and makes it injectable. */ @Binds fun bindBouncerView(bouncerViewImpl: BouncerViewImpl): BouncerView diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt index 66c6162533bf..55bc653fc737 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt @@ -17,7 +17,6 @@ package com.android.systemui.bouncer.ui.viewmodel import android.annotation.StringRes -import android.util.Log import com.android.systemui.authentication.domain.interactor.AuthenticationResult import com.android.systemui.authentication.domain.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor @@ -103,11 +102,12 @@ sealed class AuthMethodBouncerViewModel( * * @see BouncerInteractor.authenticate */ - protected fun tryAuthenticate(useAutoConfirm: Boolean = false) { + protected fun tryAuthenticate( + input: List<Any> = getInput(), + useAutoConfirm: Boolean = false, + ) { viewModelScope.launch { - Log.d("Danny", "tryAuthenticate(useAutoConfirm=$useAutoConfirm)") - val authenticationResult = interactor.authenticate(getInput(), useAutoConfirm) - Log.d("Danny", "result = $authenticationResult") + val authenticationResult = interactor.authenticate(input, useAutoConfirm) if (authenticationResult == AuthenticationResult.SKIPPED && useAutoConfirm) { return@launch } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt index c98cf317bb73..2cb98d879e69 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt @@ -17,19 +17,29 @@ package com.android.systemui.bouncer.ui.viewmodel import android.content.Context +import android.graphics.Bitmap +import androidx.core.graphics.drawable.toBitmap import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.authentication.domain.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.shared.model.Text import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.scene.shared.flag.SceneContainerFlags -import javax.inject.Inject +import com.android.systemui.telephony.domain.interactor.TelephonyInteractor +import com.android.systemui.user.ui.viewmodel.UserActionViewModel +import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel +import com.android.systemui.user.ui.viewmodel.UserViewModel +import dagger.Module +import dagger.Provides import kotlin.math.ceil import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -42,17 +52,53 @@ import kotlinx.coroutines.job import kotlinx.coroutines.launch /** Holds UI state and handles user input on bouncer UIs. */ -@SysUISingleton -class BouncerViewModel -@Inject -constructor( +class BouncerViewModel( @Application private val applicationContext: Context, @Application private val applicationScope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, private val bouncerInteractor: BouncerInteractor, authenticationInteractor: AuthenticationInteractor, flags: SceneContainerFlags, + private val telephonyInteractor: TelephonyInteractor, + selectedUser: Flow<UserViewModel>, + users: Flow<List<UserViewModel>>, + userSwitcherMenu: Flow<List<UserActionViewModel>>, ) { + val selectedUserImage: StateFlow<Bitmap?> = + selectedUser + .map { it.image.toBitmap() } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = null, + ) + + val userSwitcherDropdown: StateFlow<List<UserSwitcherDropdownItemViewModel>> = + combine( + users, + userSwitcherMenu, + ) { users, actions -> + users.map { user -> + UserSwitcherDropdownItemViewModel( + icon = Icon.Loaded(user.image, contentDescription = null), + text = user.name, + onClick = user.onClicked ?: {}, + ) + } + + actions.map { action -> + UserSwitcherDropdownItemViewModel( + icon = Icon.Resource(action.iconResourceId, contentDescription = null), + text = Text.Resource(action.textResourceId), + onClick = action.onClicked, + ) + } + } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = emptyList(), + ) + private val isInputEnabled: StateFlow<Boolean> = bouncerInteractor.isThrottled .map { !it } @@ -102,6 +148,9 @@ constructor( ), ) + val isEmergencyButtonVisible: Boolean + get() = telephonyInteractor.hasTelephonyRadio + init { if (flags.isEnabled()) { applicationScope.launch { @@ -200,4 +249,40 @@ constructor( */ val isUpdateAnimated: Boolean, ) + + data class UserSwitcherDropdownItemViewModel( + val icon: Icon, + val text: Text, + val onClick: () -> Unit, + ) +} + +@Module +object BouncerViewModelModule { + + @Provides + @SysUISingleton + fun viewModel( + @Application applicationContext: Context, + @Application applicationScope: CoroutineScope, + @Main mainDispatcher: CoroutineDispatcher, + bouncerInteractor: BouncerInteractor, + authenticationInteractor: AuthenticationInteractor, + flags: SceneContainerFlags, + telephonyInteractor: TelephonyInteractor, + userSwitcherViewModel: UserSwitcherViewModel, + ): BouncerViewModel { + return BouncerViewModel( + applicationContext = applicationContext, + applicationScope = applicationScope, + mainDispatcher = mainDispatcher, + bouncerInteractor = bouncerInteractor, + authenticationInteractor = authenticationInteractor, + flags = flags, + telephonyInteractor = telephonyInteractor, + selectedUser = userSwitcherViewModel.selectedUser, + users = userSwitcherViewModel.users, + userSwitcherMenu = userSwitcherViewModel.menu, + ) + } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt index 52adf54f8d24..d301085e823d 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt @@ -166,7 +166,8 @@ class PatternBouncerViewModel( interactor.onFalseUserInput() } - tryAuthenticate() + clearInput() + tryAuthenticate(input = pattern) } override fun clearInput() { diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt index ea3ddacd7d5e..f6e02969e4c2 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt @@ -17,5 +17,5 @@ class CommunalRepositoryImpl constructor( featureFlags: FeatureFlagsClassic, ) : CommunalRepository { - override val isCommunalEnabled = featureFlags.isEnabled(Flags.COMMUNAL_HUB) + override val isCommunalEnabled = featureFlags.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) } diff --git a/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt b/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt index 10578521f182..44b5e9989784 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt @@ -28,4 +28,8 @@ class BurnInHelperWrapper @Inject constructor() { fun burnInProgressOffset(): Float { return getBurnInProgressOffset() } + + fun burnInScale(): Float { + return getBurnInScale() + } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index c79952e03aa7..8587329de1bd 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -817,7 +817,13 @@ object Flags { val BLUETOOTH_QS_TILE_DIALOG = unreleasedFlag("bluetooth_qs_tile_dialog", teamfood = true) // TODO(b/300995746): Tracking Bug - /** Enable communal hub features. */ + /** A resource flag for whether the communal service is enabled. */ @JvmField - val COMMUNAL_HUB = resourceBooleanFlag(R.bool.config_communalServiceEnabled, "communal_hub") + val COMMUNAL_SERVICE_ENABLED = resourceBooleanFlag(R.bool.config_communalServiceEnabled, + "communal_service_enabled") + + // TODO(b/303131306): Tracking Bug + /** Whether communal hub features are enabled. */ + @JvmField + val COMMUNAL_HUB = unreleasedFlag("communal_hub") } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index 2a69ec556d4b..fde92b85cac3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -413,7 +413,7 @@ class KeyguardUnlockAnimationController @Inject constructor( fun canPerformInWindowLauncherAnimations(): Boolean { // TODO(b/278086361): Refactor in-window animations. return !featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR) && - isNexusLauncherUnderneath() && + isSupportedLauncherUnderneath() && // If the launcher is underneath, but we're about to launch an activity, don't do // the animations since they won't be visible. !notificationShadeWindowController.isLaunchingActivity && @@ -427,7 +427,7 @@ class KeyguardUnlockAnimationController @Inject constructor( */ private fun logInWindowAnimationConditions() { Log.wtf(TAG, "canPerformInWindowLauncherAnimations expected all of these to be true: ") - Log.wtf(TAG, " isNexusLauncherUnderneath: ${isNexusLauncherUnderneath()}") + Log.wtf(TAG, " isNexusLauncherUnderneath: ${isSupportedLauncherUnderneath()}") Log.wtf(TAG, " !notificationShadeWindowController.isLaunchingActivity: " + "${!notificationShadeWindowController.isLaunchingActivity}") Log.wtf(TAG, " launcherUnlockController != null: ${launcherUnlockController != null}") @@ -1050,7 +1050,7 @@ class KeyguardUnlockAnimationController @Inject constructor( // If our launcher isn't underneath, then we're unlocking to an app or custom launcher, // neither of which have a smartspace. - if (!isNexusLauncherUnderneath()) { + if (!isSupportedLauncherUnderneath()) { return false } @@ -1120,11 +1120,11 @@ class KeyguardUnlockAnimationController @Inject constructor( } /** - * Return whether the Google Nexus launcher is underneath the keyguard, vs. some other - * launcher or an app. If so, we can communicate with it to perform in-window/shared element - * transitions! + * Return whether a launcher which supports coordinated transition is underneath the keyguard, + * vs. some other launcher or an app. If so, we can communicate with it to perform + * in-window/shared element transitions! */ - fun isNexusLauncherUnderneath(): Boolean { + fun isSupportedLauncherUnderneath(): Boolean { return launcherActivityClass?.let { ActivityManagerWrapper.getInstance() .runningTask?.topActivity?.className?.equals(it) } ?: false diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index 1761ca86f588..f500017e3a33 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -137,7 +137,8 @@ constructor( occludingAppDeviceEntryMessageViewModel, chipbarCoordinator, keyguardStateController, - shadeInteractor + shadeInteractor, + { keyguardStatusViewController!!.getClockController() }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 7e826b86c9ee..7678f4d643ca 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -2832,7 +2832,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // playing in-window animations for this particular unlock since a previous unlock might // have changed the Launcher state. if (mWakeAndUnlocking - && mKeyguardUnlockAnimationControllerLazy.get().isNexusLauncherUnderneath()) { + && mKeyguardUnlockAnimationControllerLazy.get() + .isSupportedLauncherUnderneath()) { flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT; } @@ -2946,9 +2947,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } else { Log.d(TAG, "Hiding keyguard while occluded. Just hide the keyguard view and exit."); - mKeyguardViewControllerLazy.get().hide( - mSystemClock.uptimeMillis() + mHideAnimation.getStartOffset(), - mHideAnimation.getDuration()); + if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + mKeyguardViewControllerLazy.get().hide( + mSystemClock.uptimeMillis() + mHideAnimation.getStartOffset(), + mHideAnimation.getDuration()); + } + onKeyguardExitFinished(); } @@ -3286,7 +3290,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // of the in-window animations are reflected. This is needed even if we're not actually // playing in-window animations for this particular unlock since a previous unlock might // have changed the Launcher state. - if (mKeyguardUnlockAnimationControllerLazy.get().isNexusLauncherUnderneath()) { + if (mKeyguardUnlockAnimationControllerLazy.get().isSupportedLauncherUnderneath()) { flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT; } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt index b2e436cd2af6..8bf2bc395f88 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt @@ -19,17 +19,22 @@ package com.android.systemui.keyguard.domain.interactor import android.content.Context import androidx.annotation.DimenRes -import com.android.systemui.res.R 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.doze.util.BurnInHelperWrapper +import com.android.systemui.keyguard.shared.model.BurnInModel +import com.android.systemui.res.R import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn @@ -54,6 +59,18 @@ constructor( .mapLatest { burnInHelperWrapper.burnInProgressOffset() } .stateIn(scope, SharingStarted.Lazily, burnInHelperWrapper.burnInProgressOffset()) + val keyguardBurnIn: Flow<BurnInModel> = + combine( + burnInOffset(R.dimen.burn_in_prevention_offset_x, isXAxis = true), + burnInOffset(R.dimen.burn_in_prevention_offset_y, isXAxis = false).map { + it * 2 - + context.resources.getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y) + } + ) { translationX, translationY -> + BurnInModel(translationX, translationY, burnInHelperWrapper.burnInScale()) + } + .distinctUntilChanged() + /** * Use for max burn-in offsets that are NOT specified in pixels. This flow will recalculate the * max burn-in offset on any configuration changes. If the max burn-in offset is specified in diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BurnInModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BurnInModel.kt new file mode 100644 index 000000000000..02d1471ef29c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BurnInModel.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.shared.model + +/** Clock burn-in translation/scaling data */ +data class BurnInModel( + val translationX: Int = 0, + val translationY: Int = 0, + val scale: Float = 0f, + val scaleClockOnly: Boolean = false, +) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index 053727ace76d..ac4ad39a38a5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -23,7 +23,6 @@ import android.view.ViewGroup import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.animation.Interpolators -import com.android.systemui.res.R import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text import com.android.systemui.common.shared.model.TintedIcon @@ -32,12 +31,15 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.plugins.ClockController +import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.temporarydisplay.ViewPriority import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo +import javax.inject.Provider import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch @@ -57,6 +59,7 @@ object KeyguardRootViewBinder { chipbarCoordinator: ChipbarCoordinator, keyguardStateController: KeyguardStateController, shadeInteractor: ShadeInteractor, + clockControllerProvider: Provider<ClockController>?, ): DisposableHandle { val disposableHandle = view.repeatWhenAttached { @@ -83,10 +86,37 @@ object KeyguardRootViewBinder { if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { launch { - viewModel.translationY.collect { - val statusView = - view.requireViewById<View>(R.id.keyguard_status_view) - statusView.translationY = it + viewModel.translationY.collect { y -> + val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer) + burnInLayer.translationY = y + } + } + } + + if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + launch { + viewModel.translationX.collect { x -> + val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer) + burnInLayer.translationX = x + } + } + } + + if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + launch { + viewModel.scale.collect { (scale, scaleClockOnly) -> + if (scaleClockOnly) { + val largeClock = + view.findViewById<View?>(R.id.lockscreen_clock_view_large) + largeClock?.let { + it.scaleX = scale + it.scaleY = scale + } + } else { + val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer) + burnInLayer.scaleX = scale + burnInLayer.scaleY = scale + } } } } @@ -141,6 +171,7 @@ object KeyguardRootViewBinder { } } } + viewModel.clockControllerProvider = clockControllerProvider onLayoutChangeListener = OnLayoutChange(viewModel) view.addOnLayoutChangeListener(onLayoutChangeListener) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index 864e345e9c1e..231eeb59737c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -38,7 +38,6 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isInvisible import com.android.keyguard.ClockEventController import com.android.keyguard.KeyguardClockSwitch -import com.android.systemui.res.R import com.android.systemui.animation.view.LaunchableImageView import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.broadcast.BroadcastDispatcher @@ -62,6 +61,7 @@ import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessage import com.android.systemui.monet.ColorScheme import com.android.systemui.plugins.ClockController import com.android.systemui.plugins.FalsingManager +import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shared.clocks.ClockRegistry import com.android.systemui.shared.clocks.DefaultClockController @@ -320,6 +320,7 @@ constructor( chipbarCoordinator, keyguardStateController, shadeInteractor, + null, // clock provider only needed for burn in ) ) rootView.addView( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt index 1eeb0172f419..e8df1a6fdaab 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.view.layout.blueprints import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.KeyguardBlueprint +import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection @@ -51,6 +52,7 @@ constructor( defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection, splitShadeGuidelines: SplitShadeGuidelines, aodNotificationIconsSection: AodNotificationIconsSection, + aodBurnInSection: AodBurnInSection, ) : KeyguardBlueprint { override val id: String = DEFAULT @@ -66,6 +68,7 @@ constructor( defaultNotificationStackScrollLayoutSection, splitShadeGuidelines, aodNotificationIconsSection, + aodBurnInSection, ) companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt index a9885fc8defe..ce76f56fad2f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt @@ -20,6 +20,7 @@ package com.android.systemui.keyguard.ui.view.layout.blueprints import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.KeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.sections.AlignShortcutsToUdfpsSection +import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection @@ -46,6 +47,7 @@ constructor( splitShadeGuidelines: SplitShadeGuidelines, defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection, aodNotificationIconsSection: AodNotificationIconsSection, + aodBurnInSection: AodBurnInSection, ) : KeyguardBlueprint { override val id: String = SHORTCUTS_BESIDE_UDFPS @@ -61,6 +63,7 @@ constructor( defaultNotificationStackScrollLayoutSection, splitShadeGuidelines, aodNotificationIconsSection, + aodBurnInSection, ) companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt new file mode 100644 index 000000000000..09caf4505c3b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.view.layout.sections + +import android.content.Context +import android.view.View +import androidx.constraintlayout.helper.widget.Layer +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.res.R +import javax.inject.Inject + +/** Adds a layer to group elements for translation for burn-in preventation */ +class AodBurnInSection +@Inject +constructor( + private val context: Context, + private val featureFlags: FeatureFlags, +) : KeyguardSection() { + + override fun addViews(constraintLayout: ConstraintLayout) { + if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + return + } + + val statusView = constraintLayout.requireViewById<View>(R.id.keyguard_status_view) + val nic = constraintLayout.requireViewById<View>(R.id.aod_notification_icon_container) + val burnInLayer = + Layer(context).apply { + id = R.id.burn_in_layer + addView(nic) + addView(statusView) + } + constraintLayout.addView(burnInLayer) + } + + override fun bindData(constraintLayout: ConstraintLayout) { + if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + return + } + } + + override fun applyConstraints(constraintSet: ConstraintSet) { + if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + return + } + } + + override fun removeViews(constraintLayout: ConstraintLayout) { + constraintLayout.removeView(R.id.burn_in_layer) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index c49af4d0717a..89835fecd95c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -17,22 +17,32 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.util.MathUtils +import com.android.app.animation.Interpolators import com.android.systemui.common.shared.model.SharedNotificationContainerPosition +import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.shared.model.BurnInModel import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState +import com.android.systemui.plugins.ClockController import javax.inject.Inject +import javax.inject.Provider import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge @OptIn(ExperimentalCoroutinesApi::class) class KeyguardRootViewModel @Inject constructor( private val keyguardInteractor: KeyguardInteractor, + private val burnInInteractor: BurnInInteractor, ) { data class PreviewMode(val isInPreviewMode: Boolean = false) @@ -44,6 +54,8 @@ constructor( */ private val previewMode = MutableStateFlow(PreviewMode()) + public var clockControllerProvider: Provider<ClockController>? = null + /** Represents the current state of the KeyguardRootView visibility */ val keyguardRootViewVisibilityState: Flow<KeyguardRootViewVisibilityState> = keyguardInteractor.keyguardRootViewVisibilityState @@ -58,7 +70,34 @@ constructor( } } - val translationY: Flow<Float> = keyguardInteractor.keyguardTranslationY + private val burnIn: Flow<BurnInModel> = + combine(keyguardInteractor.dozeAmount, burnInInteractor.keyguardBurnIn) { dozeAmount, burnIn + -> + val interpolation = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(dozeAmount) + val useScaleOnly = + clockControllerProvider?.get()?.config?.useAlternateSmartspaceAODTransition ?: false + if (useScaleOnly) { + BurnInModel( + translationX = 0, + translationY = 0, + scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolation), + ) + } else { + BurnInModel( + translationX = MathUtils.lerp(0, burnIn.translationX, interpolation).toInt(), + translationY = MathUtils.lerp(0, burnIn.translationY, interpolation).toInt(), + scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolation), + scaleClockOnly = true, + ) + } + } + + val translationY: Flow<Float> = + merge(keyguardInteractor.keyguardTranslationY, burnIn.map { it.translationY.toFloat() }) + + val translationX: Flow<Float> = burnIn.map { it.translationX.toFloat() } + + val scale: Flow<Pair<Float, Boolean>> = burnIn.map { Pair(it.scale, it.scaleClockOnly) } /** * Puts this view-model in "preview mode", which means it's being used for UI that is rendering diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 1943b340b9b1..67531ad9926a 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -30,6 +30,8 @@ import com.android.systemui.log.LogcatEchoTrackerProd; import com.android.systemui.log.table.TableLogBuffer; import com.android.systemui.log.table.TableLogBufferFactory; import com.android.systemui.qs.QSFragmentLegacy; +import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository; +import com.android.systemui.qs.pipeline.shared.TileSpec; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.util.Compile; import com.android.systemui.util.wakelock.WakeLockLog; @@ -37,6 +39,9 @@ import com.android.systemui.util.wakelock.WakeLockLog; import dagger.Module; import dagger.Provides; +import java.util.HashMap; +import java.util.Map; + /** * Dagger module for providing instances of {@link LogBuffer}. */ @@ -173,8 +178,35 @@ public class LogModule { @Provides @SysUISingleton @QSLog - public static LogBuffer provideQuickSettingsLogBuffer(LogBufferFactory factory) { - return factory.create("QSLog", 700 /* maxSize */, false /* systrace */); + public static LogBuffer provideQuickSettingsLogBuffer( + LogBufferFactory factory, + QSPipelineFlagsRepository flags + ) { + if (flags.getPipelineTilesEnabled()) { + // we use + return factory.create("QSLog", 450 /* maxSize */, false /* systrace */); + } else { + return factory.create("QSLog", 700 /* maxSize */, false /* systrace */); + } + } + + /** + * Provides a logging buffer for all logs related to Quick Settings tiles. This LogBuffer is + * unique for each tile. + * go/qs-tile-refactor + */ + @Provides + @QSTilesDefaultLog + public static LogBuffer provideQuickSettingsTilesLogBuffer(LogBufferFactory factory) { + return factory.create("QSTileLog", 25 /* maxSize */, false /* systrace */); + } + + @Provides + @QSTilesLogBuffers + public static Map<TileSpec, LogBuffer> provideQuickSettingsTilesLogBufferCache() { + final Map<TileSpec, LogBuffer> buffers = new HashMap<>(); + // Add chatty buffers here + return buffers; } /** Provides a logging buffer for logs related to Quick Settings configuration. */ @@ -420,7 +452,7 @@ public class LogModule { /** * Provides a {@link LogBuffer} for use by - * {@link com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepositoryImpl}. + * {@link com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepositoryImpl}. */ @Provides @SysUISingleton @@ -431,7 +463,7 @@ public class LogModule { /** * Provides a {@link LogBuffer} for use by classes in the - * {@link com.android.systemui.keyguard.bouncer} package. + * {@link com.android.systemui.keyguard.bouncer} package. */ @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt new file mode 100644 index 000000000000..6575cdd69c93 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.log.dagger + +import javax.inject.Qualifier + +/** + * A default [com.android.systemui.log.LogBuffer] for QS tiles messages. It's used exclusively in + * [com.android.systemui.qs.tiles.base.logging.QSTileLogger]. If you need to increase it for you + * tile, add one to the map provided by the [QSTilesLogBuffers] + */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class QSTilesDefaultLog diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesLogBuffers.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesLogBuffers.kt new file mode 100644 index 000000000000..62d49fefeb6a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesLogBuffers.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.log.dagger + +import javax.inject.Qualifier + +/** + * Provides a map with custom [com.android.systemui.log.LogBuffer] for QS tiles messages. Add + * buffers to it when the tile needs to be more verbose and the default buffer provided by + * [QSTilesDefaultLog] is not enough. + * + * This is not a multibinding. Add new logs directly to [LogModule] + */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class QSTilesLogBuffers diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesVerboseLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesVerboseLog.java new file mode 100644 index 000000000000..b0c2f8c59deb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesVerboseLog.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.android.systemui.log.LogBuffer; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +/** + * A {@link LogBuffer} for QS tiles messages. It's used exclusively in + * {@link com.android.systemui.qs.tiles.base.logging.QSTileLogger} + */ +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface QSTilesVerboseLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index 19012e29b184..fa18b35b215e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -27,11 +27,11 @@ import androidx.viewpager.widget.ViewPager; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.UiEventLogger; -import com.android.systemui.res.R; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.QSPanel.QSTileLayout; import com.android.systemui.qs.QSPanelControllerBase.TileRecord; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.res.R; import java.util.ArrayList; import java.util.List; @@ -562,6 +562,12 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { if (shouldNotRunAnimation(tilesToReveal)) { return; } + // This method has side effects (beings the fake drag, if it returns true). If we have + // decided that we want to do a tile reveal, we do a last check to verify that we can + // actually perform a fake drag. + if (!beginFakeDrag()) { + return; + } final int lastPageNumber = mPages.size() - 1; final TileLayout lastPage = mPages.get(lastPageNumber); @@ -596,8 +602,10 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { } private boolean shouldNotRunAnimation(Set<String> tilesToReveal) { + // None of these have side effects. That way, we don't need to rely on short-circuiting + // behavior boolean noAnimationNeeded = tilesToReveal.isEmpty() || mPages.size() < 2; - boolean scrollingInProgress = getScrollX() != 0 || !beginFakeDrag(); + boolean scrollingInProgress = getScrollX() != 0 || !isFakeDragging(); // checking mRunningInTestHarness to disable animation in functional testing as it caused // flakiness and is not needed there. Alternative solutions were more complex and would // still be either potentially flaky or modify internal data. diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index 128c23745e5f..051eeb0cfaf1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -72,7 +72,8 @@ import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; import dagger.assisted.AssistedInject; -public class CustomTile extends QSTileImpl<State> implements TileChangeListener { +public class CustomTile extends QSTileImpl<State> implements TileChangeListener, + CustomTileInterface { public static final String PREFIX = "custom("; private static final long CUSTOM_STALE_TIMEOUT = DateUtils.HOUR_IN_MILLIS; @@ -181,7 +182,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener private void updateDefaultTileAndIcon() { try { PackageManager pm = mUserContext.getPackageManager(); - int flags = PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_DIRECT_BOOT_AWARE; + int flags = PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_DIRECT_BOOT_AWARE; if (isSystemApp(pm)) { flags |= PackageManager.MATCH_DISABLED_COMPONENTS; } @@ -213,7 +215,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener * Compare two icons, only works for resources. */ private boolean iconEquals(@Nullable android.graphics.drawable.Icon icon1, - @Nullable android.graphics.drawable.Icon icon2) { + @Nullable android.graphics.drawable.Icon icon2) { if (icon1 == icon2) { return true; } @@ -252,10 +254,12 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener } } + @Override public int getUser() { return mUser; } + @Override public ComponentName getComponent() { return mComponent; } @@ -265,6 +269,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener return super.populate(logMaker).setComponentName(mComponent); } + @Override public Tile getQsTile() { // TODO(b/191145007) Move to background thread safely updateDefaultTileAndIcon(); @@ -276,6 +281,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener * * @param tile tile populated with state to apply */ + @Override public void updateTileState(Tile tile, int appUid) { mServiceUid = appUid; // This comes from a binder call IQSService.updateQsTile @@ -310,10 +316,12 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mTile.setState(tile.getState()); } + @Override public void onDialogShown() { mIsShowingDialog = true; } + @Override public void onDialogHidden() { mIsShowingDialog = false; try { @@ -507,6 +515,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener return mComponent.getPackageName(); } + @Override public void startUnlockAndRun() { mActivityStarter.postQSRunnableDismissingKeyguard(() -> { try { @@ -518,8 +527,10 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener /** * Starts an {@link android.app.Activity} + * * @param pendingIntent A PendingIntent for an Activity to be launched immediately. */ + @Override public void startActivityAndCollapse(PendingIntent pendingIntent) { if (!pendingIntent.isActivity()) { Log.i(TAG, "Intent not for activity."); diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileInterface.kt b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileInterface.kt new file mode 100644 index 000000000000..9e023205c9fb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileInterface.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.external + +import android.app.PendingIntent +import android.content.ComponentName +import android.service.quicksettings.Tile + +interface CustomTileInterface { + + val user: Int + val qsTile: Tile + val component: ComponentName + + fun getTileSpec(): String + + fun refreshState() + fun updateTileState(tile: Tile, uid: Int) + + fun onDialogShown() + fun onDialogHidden() + + fun startActivityAndCollapse(pendingIntent: PendingIntent) + + fun startUnlockAndRun() +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java index fc2402258009..acee8e9ad2eb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java @@ -67,9 +67,10 @@ public class TileServices extends IQSService.Stub { static final int REDUCED_MAX_BOUND = 1; private static final String TAG = "TileServices"; - private final ArrayMap<CustomTile, TileServiceManager> mServices = new ArrayMap<>(); - private final SparseArrayMap<ComponentName, CustomTile> mTiles = new SparseArrayMap<>(); - private final ArrayMap<IBinder, CustomTile> mTokenMap = new ArrayMap<>(); + private final ArrayMap<CustomTileInterface, TileServiceManager> mServices = new ArrayMap<>(); + private final SparseArrayMap<ComponentName, CustomTileInterface> mTiles = + new SparseArrayMap<>(); + private final ArrayMap<IBinder, CustomTileInterface> mTokenMap = new ArrayMap<>(); private final Context mContext; private final Handler mMainHandler; private final Provider<Handler> mHandlerProvider; @@ -120,7 +121,7 @@ public class TileServices extends IQSService.Stub { return mHost; } - public TileServiceManager getTileWrapper(CustomTile tile) { + public TileServiceManager getTileWrapper(CustomTileInterface tile) { ComponentName component = tile.getComponent(); int userId = tile.getUser(); TileServiceManager service = onCreateTileService(component, mBroadcastDispatcher); @@ -140,7 +141,7 @@ public class TileServices extends IQSService.Stub { broadcastDispatcher, mUserTracker, mCustomTileAddedRepository, mBackgroundExecutor); } - public void freeService(CustomTile tile, TileServiceManager service) { + public void freeService(CustomTileInterface tile, TileServiceManager service) { synchronized (mServices) { service.setBindAllowed(false); service.handleDestroy(); @@ -184,7 +185,7 @@ public class TileServices extends IQSService.Stub { } } - private int verifyCaller(CustomTile tile) { + private int verifyCaller(CustomTileInterface tile) { try { String packageName = tile.getComponent().getPackageName(); int uid = mContext.getPackageManager().getPackageUidAsUser(packageName, @@ -201,7 +202,7 @@ public class TileServices extends IQSService.Stub { private void requestListening(ComponentName component) { synchronized (mServices) { int userId = mUserTracker.getUserId(); - CustomTile customTile = getTileForUserAndComponent(userId, component); + CustomTileInterface customTile = getTileForUserAndComponent(userId, component); if (customTile == null) { Log.d(TAG, "Couldn't find tile for " + component + "(" + userId + ")"); return; @@ -227,7 +228,7 @@ public class TileServices extends IQSService.Stub { @Override public void updateQsTile(Tile tile, IBinder token) { - CustomTile customTile = getTileForToken(token); + CustomTileInterface customTile = getTileForToken(token); if (customTile != null) { int uid = verifyCaller(customTile); synchronized (mServices) { @@ -247,7 +248,7 @@ public class TileServices extends IQSService.Stub { @Override public void onStartSuccessful(IBinder token) { - CustomTile customTile = getTileForToken(token); + CustomTileInterface customTile = getTileForToken(token); if (customTile != null) { verifyCaller(customTile); synchronized (mServices) { @@ -267,7 +268,7 @@ public class TileServices extends IQSService.Stub { @Override public void onShowDialog(IBinder token) { - CustomTile customTile = getTileForToken(token); + CustomTileInterface customTile = getTileForToken(token); if (customTile != null) { verifyCaller(customTile); customTile.onDialogShown(); @@ -278,7 +279,7 @@ public class TileServices extends IQSService.Stub { @Override public void onDialogHidden(IBinder token) { - CustomTile customTile = getTileForToken(token); + CustomTileInterface customTile = getTileForToken(token); if (customTile != null) { verifyCaller(customTile); Objects.requireNonNull(mServices.get(customTile)).setShowingDialog(false); @@ -288,7 +289,7 @@ public class TileServices extends IQSService.Stub { @Override public void onStartActivity(IBinder token) { - CustomTile customTile = getTileForToken(token); + CustomTileInterface customTile = getTileForToken(token); if (customTile != null) { verifyCaller(customTile); mPanelInteractor.forceCollapsePanels(); @@ -301,7 +302,7 @@ public class TileServices extends IQSService.Stub { } @VisibleForTesting - protected void startActivity(CustomTile customTile, PendingIntent pendingIntent) { + protected void startActivity(CustomTileInterface customTile, PendingIntent pendingIntent) { if (customTile != null) { verifyCaller(customTile); customTile.startActivityAndCollapse(pendingIntent); @@ -310,7 +311,7 @@ public class TileServices extends IQSService.Stub { @Override public void updateStatusIcon(IBinder token, Icon icon, String contentDescription) { - CustomTile customTile = getTileForToken(token); + CustomTileInterface customTile = getTileForToken(token); if (customTile != null) { verifyCaller(customTile); try { @@ -340,7 +341,7 @@ public class TileServices extends IQSService.Stub { @Nullable @Override public Tile getTile(IBinder token) { - CustomTile customTile = getTileForToken(token); + CustomTileInterface customTile = getTileForToken(token); if (customTile != null) { verifyCaller(customTile); return customTile.getQsTile(); @@ -367,7 +368,7 @@ public class TileServices extends IQSService.Stub { @Override public void startUnlockAndRun(IBinder token) { - CustomTile customTile = getTileForToken(token); + CustomTileInterface customTile = getTileForToken(token); if (customTile != null) { verifyCaller(customTile); customTile.startUnlockAndRun(); @@ -385,14 +386,14 @@ public class TileServices extends IQSService.Stub { } @Nullable - public CustomTile getTileForToken(IBinder token) { + public CustomTileInterface getTileForToken(IBinder token) { synchronized (mServices) { return mTokenMap.get(token); } } @Nullable - private CustomTile getTileForUserAndComponent(int userId, ComponentName component) { + private CustomTileInterface getTileForUserAndComponent(int userId, ComponentName component) { synchronized (mServices) { return mTiles.get(userId, component); } @@ -419,11 +420,6 @@ public class TileServices extends IQSService.Stub { }; private static final Comparator<TileServiceManager> SERVICE_SORT = - new Comparator<TileServiceManager>() { - @Override - public int compare(TileServiceManager left, TileServiceManager right) { - return -Integer.compare(left.getBindPriority(), right.getBindPriority()); - } - }; + (left, right) -> -Integer.compare(left.getBindPriority(), right.getBindPriority()); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/dagger/FooterActionsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/dagger/FooterActionsModule.kt index 38fe34eb8f9f..42d3f81ceed6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/dagger/FooterActionsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/dagger/FooterActionsModule.kt @@ -18,8 +18,6 @@ package com.android.systemui.qs.footer.dagger import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepository import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepositoryImpl -import com.android.systemui.qs.footer.data.repository.UserSwitcherRepository -import com.android.systemui.qs.footer.data.repository.UserSwitcherRepositoryImpl import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl import dagger.Binds @@ -28,7 +26,6 @@ import dagger.Module /** Dagger module to provide/bind footer actions singletons. */ @Module interface FooterActionsModule { - @Binds fun userSwitcherRepository(impl: UserSwitcherRepositoryImpl): UserSwitcherRepository @Binds fun foregroundServicesRepository( diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt index 8b2c3de18469..c91ed133a11e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt @@ -38,10 +38,10 @@ import com.android.systemui.qs.FgsManagerController import com.android.systemui.qs.QSSecurityFooterUtils import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepository -import com.android.systemui.qs.footer.data.repository.UserSwitcherRepository import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig import com.android.systemui.security.data.repository.SecurityRepository import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.user.data.repository.UserSwitcherRepository import com.android.systemui.user.domain.interactor.UserInteractor import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalytics.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalytics.kt new file mode 100644 index 000000000000..0d15a5b6b4d4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalytics.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.base.analytics + +import com.android.internal.logging.UiEventLogger +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.QSEvent +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import javax.inject.Inject + +/** Tracks QS tiles analytic events to [UiEventLogger]. */ +@SysUISingleton +class QSTileAnalytics +@Inject +constructor( + private val uiEventLogger: UiEventLogger, +) { + + fun trackUserAction(config: QSTileConfig, action: QSTileUserAction) { + logAction(config, action) + } + + private fun logAction(config: QSTileConfig, action: QSTileUserAction) { + uiEventLogger.logWithInstanceId( + action.getQSEvent(), + 0, + config.metricsSpec, + config.instanceId, + ) + } + + private fun QSTileUserAction.getQSEvent(): QSEvent = + when (this) { + is QSTileUserAction.Click -> QSEvent.QS_ACTION_CLICK + is QSTileUserAction.LongClick -> QSEvent.QS_ACTION_LONG_PRESS + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt new file mode 100644 index 000000000000..70a683b81f75 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.base.logging + +import androidx.annotation.GuardedBy +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.dagger.QSTilesDefaultLog +import com.android.systemui.log.dagger.QSTilesLogBuffers +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import com.android.systemui.statusbar.StatusBarState +import javax.inject.Inject +import javax.inject.Provider + +@SysUISingleton +class QSTileLogger +@Inject +constructor( + @QSTilesLogBuffers logBuffers: Map<TileSpec, LogBuffer>, + @QSTilesDefaultLog private val defaultLogBufferProvider: Provider<LogBuffer>, + private val mStatusBarStateController: StatusBarStateController, +) { + @GuardedBy("logBufferCache") private val logBufferCache = logBuffers.toMutableMap() + + /** + * Tracks user action when it's first received by the ViewModel and before it reaches the + * pipeline + */ + fun logUserAction( + userAction: QSTileUserAction, + tileSpec: TileSpec, + hasData: Boolean, + hasTileState: Boolean, + ) { + tileSpec + .getLogBuffer() + .log( + tileSpec.getLogTag(), + LogLevel.DEBUG, + { + str1 = userAction.toLogString() + int1 = mStatusBarStateController.state + bool1 = hasTileState + bool2 = hasData + }, + { + "tile $str1: " + + "statusBarState=${StatusBarState.toString(int1)}, " + + "hasState=$bool1, " + + "hasData=$bool2" + } + ) + } + + /** Tracks user action when it's rejected by false gestures */ + fun logUserActionRejectedByFalsing( + userAction: QSTileUserAction, + tileSpec: TileSpec, + ) { + tileSpec + .getLogBuffer() + .log( + tileSpec.getLogTag(), + LogLevel.DEBUG, + { str1 = userAction.toLogString() }, + { "tile $str1: rejected by falsing" } + ) + } + + /** Tracks user action when it's rejected according to the policy */ + fun logUserActionRejectedByPolicy( + userAction: QSTileUserAction, + tileSpec: TileSpec, + ) { + tileSpec + .getLogBuffer() + .log( + tileSpec.getLogTag(), + LogLevel.DEBUG, + { str1 = userAction.toLogString() }, + { "tile $str1: rejected by policy" } + ) + } + + /** + * Tracks user actions when it reaches the pipeline and mixes with the last tile state and data + */ + fun <T> logUserActionPipeline( + tileSpec: TileSpec, + userAction: QSTileUserAction, + tileState: QSTileState, + data: T, + ) { + tileSpec + .getLogBuffer() + .log( + tileSpec.getLogTag(), + LogLevel.DEBUG, + { + str1 = userAction.toLogString() + str2 = tileState.toLogString() + str3 = data.toString().take(DATA_MAX_LENGTH) + }, + { + "tile $str1 pipeline: " + + "statusBarState=${StatusBarState.toString(int1)}, " + + "state=$str2, " + + "data=$str3" + } + ) + } + + /** Tracks state changes based on the data and trigger event. */ + fun <T> logStateUpdate( + tileSpec: TileSpec, + trigger: StateUpdateTrigger, + tileState: QSTileState, + data: T, + ) { + tileSpec + .getLogBuffer() + .log( + tileSpec.getLogTag(), + LogLevel.DEBUG, + { + str1 = trigger.toLogString() + str2 = tileState.toLogString() + str3 = data.toString().take(DATA_MAX_LENGTH) + }, + { "tile state update: trigger=$str1, state=$str2, data=$str3" } + ) + } + + private fun TileSpec.getLogTag(): String = "${TAG_FORMAT_PREFIX}_${this.spec}" + + private fun TileSpec.getLogBuffer(): LogBuffer = + synchronized(logBufferCache) { + logBufferCache.getOrPut(this) { defaultLogBufferProvider.get() } + } + + private fun StateUpdateTrigger.toLogString(): String = + when (this) { + is StateUpdateTrigger.ForceUpdate -> "force" + is StateUpdateTrigger.InitialRequest -> "init" + is StateUpdateTrigger.UserAction<*> -> action.toLogString() + } + + private fun QSTileUserAction.toLogString(): String = + when (this) { + is QSTileUserAction.Click -> "click" + is QSTileUserAction.LongClick -> "long click" + } + + /* Shortened version of a data class toString() */ + private fun QSTileState.toLogString(): String = + "[label=$label, " + + "state=$activationState, " + + "s_label=$secondaryLabel, " + + "cd=$contentDescription, " + + "sd=$stateDescription, " + + "svi=$sideViewIcon, " + + "enabled=$enabledState, " + + "a11y=$expandedAccessibilityClassName" + + "]" + + private companion object { + const val TAG_FORMAT_PREFIX = "QSLog" + const val DATA_MAX_LENGTH = 50 + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt index 58a335e462a1..2114751ef57b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt @@ -20,12 +20,15 @@ import androidx.annotation.CallSuper import androidx.annotation.VisibleForTesting import com.android.internal.util.Preconditions import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger +import com.android.systemui.qs.tiles.base.logging.QSTileLogger import com.android.systemui.qs.tiles.viewmodel.QSTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileLifecycle import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy @@ -33,6 +36,7 @@ import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel import com.android.systemui.util.kotlin.sample +import com.android.systemui.util.kotlin.throttle import dagger.assisted.Assisted import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineDispatcher @@ -70,6 +74,9 @@ constructor( private val tileDataInteractor: QSTileDataInteractor<DATA_TYPE>, private val mapper: QSTileDataToStateMapper<DATA_TYPE>, private val disabledByPolicyInteractor: DisabledByPolicyInteractor, + private val falsingManager: FalsingManager, + private val qsTileAnalytics: QSTileAnalytics, + private val qsTileLogger: QSTileLogger, private val backgroundDispatcher: CoroutineDispatcher, private val tileScope: CoroutineScope, ) : QSTileViewModel { @@ -81,6 +88,9 @@ constructor( @Assisted tileDataInteractor: QSTileDataInteractor<DATA_TYPE>, @Assisted mapper: QSTileDataToStateMapper<DATA_TYPE>, disabledByPolicyInteractor: DisabledByPolicyInteractor, + falsingManager: FalsingManager, + qsTileAnalytics: QSTileAnalytics, + qsTileLogger: QSTileLogger, @Background backgroundDispatcher: CoroutineDispatcher, ) : this( config, @@ -88,6 +98,9 @@ constructor( tileDataInteractor, mapper, disabledByPolicyInteractor, + falsingManager, + qsTileAnalytics, + qsTileLogger, backgroundDispatcher, CoroutineScope(SupervisorJob()) ) @@ -98,8 +111,10 @@ constructor( MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) private val forceUpdates: MutableSharedFlow<Unit> = MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) + private val spec + get() = config.tileSpec - private lateinit var tileData: SharedFlow<DATA_TYPE> + private lateinit var tileData: SharedFlow<DataWithTrigger<DATA_TYPE>> override lateinit var state: SharedFlow<QSTileState> override val isAvailable: StateFlow<Boolean> = @@ -128,8 +143,14 @@ constructor( @CallSuper override fun onActionPerformed(userAction: QSTileUserAction) { - Preconditions.checkState(tileData.replayCache.isNotEmpty()) Preconditions.checkState(currentLifeState == QSTileLifecycle.ALIVE) + + qsTileLogger.logUserAction( + userAction, + spec, + tileData.replayCache.isNotEmpty(), + state.replayCache.isNotEmpty() + ) userInputs.tryEmit(userAction) } @@ -142,7 +163,16 @@ constructor( state = tileData // TODO(b/299908705): log data and corresponding tile state - .map { mapper.map(config, it) } + .map { dataWithTrigger -> + mapper.map(config, dataWithTrigger.data).also { state -> + qsTileLogger.logStateUpdate( + spec, + dataWithTrigger.trigger, + state, + dataWithTrigger.data + ) + } + } .flowOn(backgroundDispatcher) .shareIn( tileScope, @@ -158,7 +188,7 @@ constructor( currentLifeState = lifecycle } - private fun createTileDataFlow(): SharedFlow<DATA_TYPE> = + private fun createTileDataFlow(): SharedFlow<DataWithTrigger<DATA_TYPE>> = userIds .flatMapLatest { userId -> merge( @@ -180,7 +210,7 @@ constructor( request.trigger.tileData as DATA_TYPE, ) } - dataFlow + dataFlow.map { DataWithTrigger(it, request.trigger) } } .flowOn(backgroundDispatcher) .shareIn( @@ -193,21 +223,53 @@ constructor( data class StateWithData<T>(val state: QSTileState, val data: T) return when (config.policy) { - is QSTilePolicy.NoRestrictions -> userInputs - is QSTilePolicy.Restricted -> - userInputs.filter { - val result = - disabledByPolicyInteractor.isDisabled(userId, config.policy.userRestriction) - !disabledByPolicyInteractor.handlePolicyResult(result) + is QSTilePolicy.NoRestrictions -> userInputs + is QSTilePolicy.Restricted -> + userInputs.filter { action -> + val result = + disabledByPolicyInteractor.isDisabled( + userId, + config.policy.userRestriction + ) + !disabledByPolicyInteractor.handlePolicyResult(result).also { isDisabled -> + if (isDisabled) { + qsTileLogger.logUserActionRejectedByPolicy(action, spec) + } + } + } + } + .filter { action -> + val isFalseAction = + when (action) { + is QSTileUserAction.Click -> + falsingManager.isFalseTap(FalsingManager.LOW_PENALTY) + is QSTileUserAction.LongClick -> + falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY) + } + if (isFalseAction) { + qsTileLogger.logUserActionRejectedByFalsing(action, spec) } - // Skip the input until there is some data - }.sample(state.combine(tileData) { state, data -> StateWithData(state, data) }) { - input, - stateWithData -> - StateUpdateTrigger.UserAction(input, stateWithData.state, stateWithData.data) - } + !isFalseAction + } + .throttle(500) + // Skip the input until there is some data + .sample(state.combine(tileData) { state, data -> StateWithData(state, data) }) { + input, + stateWithData -> + StateUpdateTrigger.UserAction(input, stateWithData.state, stateWithData.data).also { + qsTileLogger.logUserActionPipeline( + spec, + it.action, + stateWithData.state, + stateWithData.data + ) + qsTileAnalytics.trackUserAction(config, it.action) + } + } } + private data class DataWithTrigger<T>(val data: T, val trigger: StateUpdateTrigger) + interface Factory<T> { /** diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt index efad9ec548f9..8957fc3efb9c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt @@ -40,7 +40,7 @@ constructor( @Application private val coroutineScope: CoroutineScope, ) { - internal val updateBluetoothStateFlow: StateFlow<Boolean?> = + internal val bluetoothStateUpdate: StateFlow<Boolean?> = conflatedCallbackFlow { val listener = object : BluetoothCallback { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt index 6815a7325081..8ae2dc227998 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt @@ -26,6 +26,8 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.Switch import android.widget.TextView +import androidx.recyclerview.widget.AsyncListDiffer +import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.android.internal.logging.UiEventLogger @@ -42,24 +44,26 @@ import kotlinx.coroutines.flow.asStateFlow internal class BluetoothTileDialog constructor( private val bluetoothToggleInitialValue: Boolean, + private val subtitleResIdInitialValue: Int, private val bluetoothTileDialogCallback: BluetoothTileDialogCallback, private val uiEventLogger: UiEventLogger, context: Context, ) : SystemUIDialog(context, DEFAULT_THEME, DEFAULT_DISMISS_ON_DEVICE_LOCK) { - private val mutableBluetoothStateSwitchedFlow: MutableStateFlow<Boolean> = + private val mutableBluetoothStateToggle: MutableStateFlow<Boolean> = MutableStateFlow(bluetoothToggleInitialValue) - internal val bluetoothStateSwitchedFlow - get() = mutableBluetoothStateSwitchedFlow.asStateFlow() + internal val bluetoothStateToggle + get() = mutableBluetoothStateToggle.asStateFlow() - private val mutableClickedFlow: MutableSharedFlow<Pair<DeviceItem, Int>> = + private val mutableDeviceItemClick: MutableSharedFlow<DeviceItem> = MutableSharedFlow(extraBufferCapacity = 1) - internal val deviceItemClickedFlow - get() = mutableClickedFlow.asSharedFlow() + internal val deviceItemClick + get() = mutableDeviceItemClick.asSharedFlow() private val deviceItemAdapter: Adapter = Adapter(bluetoothTileDialogCallback) private lateinit var toggleView: Switch + private lateinit var subtitleTextView: TextView private lateinit var doneButton: View private lateinit var seeAllViewGroup: View private lateinit var pairNewDeviceViewGroup: View @@ -74,6 +78,7 @@ constructor( setContentView(LayoutInflater.from(context).inflate(R.layout.bluetooth_tile_dialog, null)) toggleView = requireViewById(R.id.bluetooth_toggle) + subtitleTextView = requireViewById(R.id.bluetooth_tile_dialog_subtitle) as TextView doneButton = requireViewById(R.id.done_button) seeAllViewGroup = requireViewById(R.id.see_all_layout_group) pairNewDeviceViewGroup = requireViewById(R.id.pair_new_device_layout_group) @@ -84,6 +89,7 @@ constructor( setupToggle() setupRecyclerView() + subtitleTextView.text = context.getString(subtitleResIdInitialValue) doneButton.setOnClickListener { dismiss() } seeAllText.setOnClickListener { bluetoothTileDialogCallback.onSeeAllClicked(it) } pairNewDeviceText.setOnClickListener { @@ -91,7 +97,6 @@ constructor( } } - // TODO(b/298124674): use DiffUtil or AsyncListDiffer to avoid updating the whole list internal fun onDeviceItemUpdated( deviceItem: List<DeviceItem>, showSeeAll: Boolean, @@ -102,18 +107,15 @@ constructor( deviceItemAdapter.refreshDeviceItemList(deviceItem) } - internal fun onDeviceItemUpdatedAtPosition(deviceItem: DeviceItem, position: Int) { - deviceItemAdapter.refreshDeviceItem(deviceItem, position) - } - - internal fun onBluetoothStateUpdated(isEnabled: Boolean) { + internal fun onBluetoothStateUpdated(isEnabled: Boolean, subtitleResId: Int) { toggleView.isChecked = isEnabled + subtitleTextView.text = context.getString(subtitleResId) } private fun setupToggle() { toggleView.isChecked = bluetoothToggleInitialValue toggleView.setOnCheckedChangeListener { _, isChecked -> - mutableBluetoothStateSwitchedFlow.value = isChecked + mutableBluetoothStateToggle.value = isChecked uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_TOGGLE_CLICKED) } } @@ -128,7 +130,32 @@ constructor( internal inner class Adapter(private val onClickCallback: BluetoothTileDialogCallback) : RecyclerView.Adapter<Adapter.DeviceItemViewHolder>() { - private val deviceItem: MutableList<DeviceItem> = mutableListOf() + private val diffUtilCallback = + object : DiffUtil.ItemCallback<DeviceItem>() { + override fun areItemsTheSame( + deviceItem1: DeviceItem, + deviceItem2: DeviceItem + ): Boolean { + return deviceItem1.cachedBluetoothDevice == deviceItem2.cachedBluetoothDevice + } + + override fun areContentsTheSame( + deviceItem1: DeviceItem, + deviceItem2: DeviceItem + ): Boolean { + return deviceItem1.type == deviceItem2.type && + deviceItem1.cachedBluetoothDevice == deviceItem2.cachedBluetoothDevice && + deviceItem1.deviceName == deviceItem2.deviceName && + deviceItem1.connectionSummary == deviceItem2.connectionSummary && + // Ignored the icon drawable + deviceItem1.iconWithDescription?.second == + deviceItem2.iconWithDescription?.second && + deviceItem1.background == deviceItem2.background && + deviceItem1.isEnabled == deviceItem2.isEnabled + } + } + + private val asyncListDiffer = AsyncListDiffer(this, diffUtilCallback) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DeviceItemViewHolder { val view = @@ -137,29 +164,21 @@ constructor( return DeviceItemViewHolder(view) } - override fun getItemCount() = deviceItem.size + override fun getItemCount() = asyncListDiffer.currentList.size override fun onBindViewHolder(holder: DeviceItemViewHolder, position: Int) { val item = getItem(position) - holder.bind(item, position, onClickCallback) + holder.bind(item, onClickCallback) } - internal fun getItem(position: Int) = deviceItem[position] + internal fun getItem(position: Int) = asyncListDiffer.currentList[position] internal fun refreshDeviceItemList(updated: List<DeviceItem>) { - deviceItem.clear() - deviceItem.addAll(updated) - notifyDataSetChanged() - } - - internal fun refreshDeviceItem(updated: DeviceItem, position: Int) { - deviceItem[position] = updated - notifyItemChanged(position) + asyncListDiffer.submitList(updated) } internal inner class DeviceItemViewHolder(view: View) : RecyclerView.ViewHolder(view) { private val container = view.requireViewById<View>(R.id.bluetooth_device_row) - private val deviceView = view.requireViewById<View>(R.id.bluetooth_device) private val nameView = view.requireViewById<TextView>(R.id.bluetooth_device_name) private val summaryView = view.requireViewById<TextView>(R.id.bluetooth_device_summary) private val iconView = view.requireViewById<ImageView>(R.id.bluetooth_device_icon) @@ -167,17 +186,15 @@ constructor( internal fun bind( item: DeviceItem, - position: Int, deviceItemOnClickCallback: BluetoothTileDialogCallback ) { container.apply { isEnabled = item.isEnabled - alpha = item.alpha - background = item.background - } - deviceView.setOnClickListener { - mutableClickedFlow.tryEmit(Pair(item, position)) - uiEventLogger.log(BluetoothTileDialogUiEvent.DEVICE_CLICKED) + background = item.background?.let { context.getDrawable(it) } + setOnClickListener { + mutableDeviceItemClick.tryEmit(item) + uiEventLogger.log(BluetoothTileDialogUiEvent.DEVICE_CLICKED) + } } nameView.text = item.deviceName summaryView.text = item.connectionSummary @@ -195,8 +212,6 @@ constructor( } internal companion object { - const val ENABLED_ALPHA = 1.0f - const val DISABLED_ALPHA = 0.3f const val MAX_DEVICE_ITEM_ENTRY = 3 const val ACTION_BLUETOOTH_DEVICE_DETAILS = "com.android.settings.BLUETOOTH_DEVICE_DETAIL_SETTINGS" diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt index 012484f2f0a6..97e178371f2d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt @@ -21,7 +21,9 @@ import android.content.Intent import android.os.Bundle import android.view.View import androidx.annotation.VisibleForTesting +import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.UiEventLogger +import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -31,6 +33,7 @@ import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Compan import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.ACTION_PAIR_NEW_DEVICE import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.MAX_DEVICE_ITEM_ENTRY +import com.android.systemui.res.R import com.android.systemui.statusbar.phone.SystemUIDialog import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -73,14 +76,26 @@ constructor( job = coroutineScope.launch(mainDispatcher) { dialog = createBluetoothTileDialog(context) - view?.let { dialogLaunchAnimator.showFromView(dialog!!, it) } ?: dialog!!.show() + view?.let { + dialogLaunchAnimator.showFromView( + dialog!!, + it, + animateBackgroundBoundsChange = true, + cuj = + DialogCuj( + InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, + INTERACTION_JANK_TAG + ) + ) + } + ?: dialog!!.show() updateDeviceItemJob?.cancel() updateDeviceItemJob = launch { deviceItemInteractor.updateDeviceItems(context) } - bluetoothStateInteractor.updateBluetoothStateFlow + bluetoothStateInteractor.bluetoothStateUpdate .filterNotNull() .onEach { - dialog!!.onBluetoothStateUpdated(it) + dialog!!.onBluetoothStateUpdated(it, getSubtitleResId(it)) updateDeviceItemJob?.cancel() updateDeviceItemJob = launch { deviceItemInteractor.updateDeviceItems(context) @@ -88,7 +103,7 @@ constructor( } .launchIn(this) - deviceItemInteractor.updateDeviceItemsFlow + deviceItemInteractor.deviceItemUpdateRequest .onEach { updateDeviceItemJob?.cancel() updateDeviceItemJob = launch { @@ -97,7 +112,7 @@ constructor( } .launchIn(this) - deviceItemInteractor.deviceItemFlow + deviceItemInteractor.deviceItemUpdate .filterNotNull() .onEach { dialog!!.onDeviceItemUpdated( @@ -109,17 +124,13 @@ constructor( .launchIn(this) dialog!! - .bluetoothStateSwitchedFlow + .bluetoothStateToggle .onEach { bluetoothStateInteractor.isBluetoothEnabled = it } .launchIn(this) dialog!! - .deviceItemClickedFlow - .onEach { - if (deviceItemInteractor.updateDeviceItemOnClick(it.first)) { - dialog!!.onDeviceItemUpdatedAtPosition(it.first, it.second) - } - } + .deviceItemClick + .onEach { deviceItemInteractor.updateDeviceItemOnClick(it) } .launchIn(this) } } @@ -127,6 +138,7 @@ constructor( private fun createBluetoothTileDialog(context: Context): BluetoothTileDialog { return BluetoothTileDialog( bluetoothStateInteractor.isBluetoothEnabled, + getSubtitleResId(bluetoothStateInteractor.isBluetoothEnabled), this@BluetoothTileDialogViewModel, uiEventLogger, context @@ -175,6 +187,13 @@ constructor( ) } } + + companion object { + private const val INTERACTION_JANK_TAG = "bluetooth_tile_dialog" + private fun getSubtitleResId(isBluetoothEnabled: Boolean) = + if (isBluetoothEnabled) R.string.quick_settings_bluetooth_tile_subtitle + else R.string.bt_is_off + } } internal interface BluetoothTileDialogCallback { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt index 03ae5e88f1b0..50eaf38f20d6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt @@ -34,7 +34,6 @@ package com.android.systemui.qs.tiles.dialog.bluetooth import android.graphics.drawable.Drawable import com.android.settingslib.bluetooth.CachedBluetoothDevice -import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.ENABLED_ALPHA enum class DeviceItemType { AVAILABLE_MEDIA_BLUETOOTH_DEVICE, @@ -48,7 +47,6 @@ data class DeviceItem( val deviceName: String = "", val connectionSummary: String = "", val iconWithDescription: Pair<Drawable, String>? = null, - val background: Drawable? = null, - var isEnabled: Boolean = true, - var alpha: Float = ENABLED_ALPHA + val background: Int? = null, + var isEnabled: Boolean = true ) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt index a16a9f1f1017..8c22614f9c31 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt @@ -24,6 +24,8 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.systemui.res.R private val backgroundOn = R.drawable.settingslib_switch_bar_bg_on +private val backgroundOff = R.drawable.bluetooth_tile_dialog_bg_off +private val backgroundOffBusy = R.drawable.bluetooth_tile_dialog_bg_off_busy private val connected = R.string.quick_settings_bluetooth_device_connected private val saved = R.string.quick_settings_bluetooth_device_saved @@ -57,11 +59,8 @@ internal class AvailableMediaDeviceItemFactory : DeviceItemFactory() { BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p -> Pair(p.first, p.second) }, - background = context.getDrawable(backgroundOn), + background = backgroundOn, isEnabled = !cachedDevice.isBusy, - alpha = - if (cachedDevice.isBusy) BluetoothTileDialog.DISABLED_ALPHA - else BluetoothTileDialog.ENABLED_ALPHA, ) } } @@ -85,10 +84,8 @@ internal class ConnectedDeviceItemFactory : DeviceItemFactory() { BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p -> Pair(p.first, p.second) }, + background = backgroundOn, isEnabled = !cachedDevice.isBusy, - alpha = - if (cachedDevice.isBusy) BluetoothTileDialog.DISABLED_ALPHA - else BluetoothTileDialog.ENABLED_ALPHA, ) } } @@ -112,10 +109,8 @@ internal class SavedDeviceItemFactory : DeviceItemFactory() { BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p -> Pair(p.first, p.second) }, + background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff, isEnabled = !cachedDevice.isBusy, - alpha = - if (cachedDevice.isBusy) BluetoothTileDialog.DISABLED_ALPHA - else BluetoothTileDialog.ENABLED_ALPHA, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt index fcd0ce6807fd..14d24f952c8d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt @@ -55,11 +55,12 @@ constructor( @Background private val backgroundDispatcher: CoroutineDispatcher, ) { - private val mutableDeviceItemFlow: MutableStateFlow<List<DeviceItem>?> = MutableStateFlow(null) - internal val deviceItemFlow - get() = mutableDeviceItemFlow.asStateFlow() + private val mutableDeviceItemUpdate: MutableStateFlow<List<DeviceItem>?> = + MutableStateFlow(null) + internal val deviceItemUpdate + get() = mutableDeviceItemUpdate.asStateFlow() - internal val updateDeviceItemsFlow: SharedFlow<Unit> = + internal val deviceItemUpdateRequest: SharedFlow<Unit> = conflatedCallbackFlow { val listener = object : BluetoothCallback { @@ -120,7 +121,7 @@ constructor( withContext(backgroundDispatcher) { val mostRecentlyConnectedDevices = bluetoothAdapter?.mostRecentlyConnectedDevices - mutableDeviceItemFlow.value = + mutableDeviceItemUpdate.value = bluetoothTileDialogRepository.cachedDevices .mapNotNull { cachedDevice -> deviceItemFactoryList @@ -143,28 +144,20 @@ constructor( ) } - internal fun updateDeviceItemOnClick(deviceItem: DeviceItem): Boolean { - var isClicked = false + internal fun updateDeviceItemOnClick(deviceItem: DeviceItem) { when (deviceItem.type) { DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> { if (!BluetoothUtils.isActiveMediaDevice(deviceItem.cachedBluetoothDevice)) { deviceItem.cachedBluetoothDevice.setActive() uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_DEVICE_SET_ACTIVE) - isClicked = true } } DeviceItemType.CONNECTED_BLUETOOTH_DEVICE -> {} DeviceItemType.SAVED_BLUETOOTH_DEVICE -> { deviceItem.cachedBluetoothDevice.connect() uiEventLogger.log(BluetoothTileDialogUiEvent.SAVED_DEVICE_CONNECT) - isClicked = true } } - if (isClicked) { - deviceItem.isEnabled = false - deviceItem.alpha = BluetoothTileDialog.DISABLED_ALPHA - } - return isClicked } internal fun setDeviceItemFactoryListForTesting(list: List<DeviceItemFactory>) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt index 1a6cf99ab810..4a3bcae17fd0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt @@ -26,6 +26,7 @@ data class QSTileConfig( val tileIcon: Icon, @StringRes val tileLabelRes: Int, val instanceId: InstanceId, + val metricsSpec: String = tileSpec.spec, val policy: QSTilePolicy = QSTilePolicy.NoRestrictions, ) diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java index 3501b6bc045e..bd43307dba5a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java @@ -456,6 +456,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList currentUser); } catch (IOException | IllegalStateException e) { Log.e(TAG, "Error saving screen recording: " + e.getMessage()); + e.printStackTrace(); showErrorToast(R.string.screenrecord_save_error); mNotificationManager.cancelAsUser(null, mNotificationId, currentUser); } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index a821729e486d..d3d38e5c21cd 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -4619,7 +4619,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth()); // Update Clock Pivot (used by anti-burnin transformations) - mKeyguardStatusViewController.updatePivot(mView.getWidth(), mView.getHeight()); + if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + mKeyguardStatusViewController.updatePivot(mView.getWidth(), mView.getHeight()); + } int oldMaxHeight = mQsController.updateHeightsOnShadeLayoutChange(); positionClockAndNotifications(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt index 249c83166c51..e2de37fcbcbe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.dagger import com.android.systemui.CoreStartable import com.android.systemui.statusbar.core.StatusBarInitializer +import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepository +import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryImpl import com.android.systemui.statusbar.data.repository.StatusBarModeRepository import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryImpl import com.android.systemui.statusbar.phone.LightBarController @@ -49,6 +51,11 @@ abstract class StatusBarModule { abstract fun bindStatusBarModeRepositoryStart(impl: StatusBarModeRepositoryImpl): CoreStartable @Binds + abstract fun bindKeyguardStatusBarRepository( + impl: KeyguardStatusBarRepositoryImpl + ): KeyguardStatusBarRepository + + @Binds @IntoMap @ClassKey(OngoingCallController::class) abstract fun bindOngoingCallController(impl: OngoingCallController): CoreStartable diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt new file mode 100644 index 000000000000..8136de9b7ac4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.data.repository + +import android.content.Context +import com.android.internal.R +import com.android.systemui.common.coroutine.ConflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.user.data.repository.UserSwitcherRepository +import javax.inject.Inject +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge + +/** + * Repository for data that's specific to the status bar **on keyguard**. For data that applies to + * all status bars, use [StatusBarModeRepository]. + */ +interface KeyguardStatusBarRepository { + /** True if we can show the user switcher on keyguard and false otherwise. */ + val isKeyguardUserSwitcherEnabled: Flow<Boolean> +} + +@SysUISingleton +class KeyguardStatusBarRepositoryImpl +@Inject +constructor( + context: Context, + configurationController: ConfigurationController, + userSwitcherRepository: UserSwitcherRepository, +) : KeyguardStatusBarRepository { + private val relevantConfigChanges: Flow<Unit> = + ConflatedCallbackFlow.conflatedCallbackFlow { + val callback = + object : ConfigurationController.ConfigurationListener { + override fun onSmallestScreenWidthChanged() { + trySend(Unit) + } + + override fun onDensityOrFontScaleChanged() { + trySend(Unit) + } + } + configurationController.addCallback(callback) + awaitClose { configurationController.removeCallback(callback) } + } + + private val isKeyguardUserSwitcherConfigEnabled: Flow<Boolean> = + // The config depends on screen size and user enabled settings, so re-fetch whenever any of + // those change. + merge(userSwitcherRepository.isEnabled.map {}, relevantConfigChanges).map { + context.resources.getBoolean(R.bool.config_keyguardUserSwitcher) + } + + /** True if we can show the user switcher on keyguard and false otherwise. */ + override val isKeyguardUserSwitcherEnabled: Flow<Boolean> = + combine( + userSwitcherRepository.isEnabled, + isKeyguardUserSwitcherConfigEnabled, + ) { isEnabled, isKeyguardEnabled -> + isEnabled && isKeyguardEnabled + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/KeyguardStatusBarInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/KeyguardStatusBarInteractor.kt new file mode 100644 index 000000000000..e0c30e5ec5c4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/KeyguardStatusBarInteractor.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepository +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +@SysUISingleton +class KeyguardStatusBarInteractor +@Inject +constructor( + keyguardStatusBarRepository: KeyguardStatusBarRepository, +) { + /** True if we can show the user switcher on keyguard and false otherwise. */ + val isKeyguardUserSwitcherEnabled: Flow<Boolean> = + keyguardStatusBarRepository.isKeyguardUserSwitcherEnabled +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt index a54687c6c46b..20241c323ab6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt @@ -30,7 +30,6 @@ import com.android.app.animation.Interpolators import com.android.internal.statusbar.StatusBarIcon import com.android.internal.util.ContrastColorUtil import com.android.settingslib.Utils -import com.android.systemui.res.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.demomode.DemoMode import com.android.systemui.demomode.DemoModeController @@ -39,6 +38,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.flags.ViewRefactorFlag import com.android.systemui.plugins.DarkIconDispatcher import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.res.R import com.android.systemui.statusbar.CrossFadeHelper import com.android.systemui.statusbar.NotificationListener import com.android.systemui.statusbar.NotificationMediaManager @@ -104,6 +104,7 @@ constructor( private val contrastColorUtil: ContrastColorUtil = ContrastColorUtil.getInstance(context) private val updateStatusBarIcons = Runnable { updateStatusBarIcons() } private val shelfRefactor = ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR) + private val statusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW) private val tintAreas = ArrayList<Rect>() private var iconSize = 0 @@ -275,7 +276,9 @@ constructor( return } if (screenOffAnimationController.shouldAnimateAodIcons()) { - aodIcons!!.translationY = -aodIconAppearTranslation.toFloat() + if (!statusViewMigrated) { + aodIcons!!.translationY = -aodIconAppearTranslation.toFloat() + } aodIcons!!.alpha = 0f animateInAodIconTranslation() aodIcons!! @@ -286,7 +289,9 @@ constructor( .start() } else { aodIcons!!.alpha = 1.0f - aodIcons!!.translationY = 0f + if (!statusViewMigrated) { + aodIcons!!.translationY = 0f + } } } @@ -598,12 +603,14 @@ constructor( } private fun animateInAodIconTranslation() { - aodIcons!! - .animate() - .setInterpolator(Interpolators.DECELERATE_QUINT) - .translationY(0f) - .setDuration(AOD_ICONS_APPEAR_DURATION) - .start() + if (!statusViewMigrated) { + aodIcons!! + .animate() + .setInterpolator(Interpolators.DECELERATE_QUINT) + .translationY(0f) + .setDuration(AOD_ICONS_APPEAR_DURATION) + .start() + } } private fun reloadAodColor() { @@ -670,7 +677,9 @@ constructor( } } else { aodIcons!!.alpha = 1.0f - aodIcons!!.translationY = 0f + if (!statusViewMigrated) { + aodIcons!!.translationY = 0f + } aodIcons!!.visibility = if (visible) View.VISIBLE else View.INVISIBLE } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index 4e81d0c7cbdc..69453c65f57d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -127,7 +127,8 @@ public class StackStateAnimator { ArrayList<NotificationStackScrollLayout.AnimationEvent> mAnimationEvents, long additionalDelay) { - processAnimationEvents(mAnimationEvents); + // Animation events might generate custom animations, which are started async + boolean anyCustomAnimationCreated = processAnimationEvents(mAnimationEvents); int childCount = mHostLayout.getChildCount(); mAnimationFilter.applyCombination(mNewEvents); @@ -150,8 +151,8 @@ public class StackStateAnimator { initAnimationProperties(child, viewState, animationStaggerCount); viewState.animateTo(child, mAnimationProperties); } - if (!isRunning()) { - // no child has preformed any animation, lets finish + if (!isRunning() && !anyCustomAnimationCreated) { + // no child has performed any animation or is about to animate, lets finish onAnimationFinished(); } mHeadsUpAppearChildren.clear(); @@ -335,12 +336,15 @@ public class StackStateAnimator { } /** - * Process the animationEvents for a new animation + * Process the animationEvents for a new animation. Here is the place to do something custom, + * like to modify the ViewState or to create a custom animation for an event. * * @param animationEvents the animation events for the animation to perform + * @return true if any custom animation was created */ - private void processAnimationEvents( + private boolean processAnimationEvents( ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents) { + boolean needsCustomAnimation = false; for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) { final ExpandableView changingView = (ExpandableView) event.mChangingView; boolean loggable = false; @@ -425,7 +429,8 @@ public class StackStateAnimator { } changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR, 0 /* delay */, translationDirection, false /* isHeadsUpAppear */, - postAnimation, null); + postAnimation, getGlobalAnimationFinishedListener()); + needsCustomAnimation = true; } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) { if (mHostLayout.isFullySwipedOut(changingView)) { @@ -479,7 +484,6 @@ public class StackStateAnimator { needsAnimation = false; } } - if (needsAnimation) { // We need to add the global animation listener, since once no animations are // running anymore, the panel will instantly hide itself. We need to wait until @@ -503,9 +507,11 @@ public class StackStateAnimator { } else if (endRunnable != null) { endRunnable.run(); } + needsCustomAnimation |= needsAnimation; } mNewEvents.add(event); } + return needsCustomAnimation; } public void animateOverScrollToAmount(float targetAmount, final boolean onTop, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt index 430b0e9fab72..63591d7d6f51 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt @@ -21,12 +21,14 @@ import android.content.Context import android.content.pm.PackageManager import android.hardware.biometrics.BiometricSourceType import android.provider.Settings +import androidx.annotation.VisibleForTesting import com.android.systemui.Dumpable -import com.android.systemui.res.R import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.shade.ShadeExpansionStateManager +import com.android.systemui.res.R +import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm @@ -35,6 +37,10 @@ import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POST import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.tuner.TunerService +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch import java.io.PrintWriter import javax.inject.Inject @@ -43,6 +49,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr private val mKeyguardStateController: KeyguardStateController private val statusBarStateController: StatusBarStateController + private val shadeRepository: ShadeRepository private val devicePostureController: DevicePostureController @BypassOverride private val bypassOverride: Int private var hasFaceFeature: Boolean @@ -107,16 +114,18 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr @Inject constructor( context: Context, + @Application applicationScope: CoroutineScope, tunerService: TunerService, statusBarStateController: StatusBarStateController, lockscreenUserManager: NotificationLockscreenUserManager, keyguardStateController: KeyguardStateController, - shadeExpansionStateManager: ShadeExpansionStateManager, + shadeRepository: ShadeRepository, devicePostureController: DevicePostureController, dumpManager: DumpManager ) { this.mKeyguardStateController = keyguardStateController this.statusBarStateController = statusBarStateController + this.shadeRepository = shadeRepository this.devicePostureController = devicePostureController bypassOverride = context.resources.getInteger(R.integer.config_face_unlock_bypass_override) @@ -128,6 +137,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr return } + if (configFaceAuthSupportedPosture != DEVICE_POSTURE_UNKNOWN) { devicePostureController.addCallback { posture -> if (postureState != posture) { @@ -137,7 +147,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr } } - dumpManager.registerDumpable("KeyguardBypassController", this) + dumpManager.registerNormalDumpable("KeyguardBypassController", this) statusBarStateController.addCallback(object : StatusBarStateController.StateListener { override fun onStateChanged(newState: Int) { if (newState != StatusBarState.KEYGUARD) { @@ -146,27 +156,36 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr } }) - shadeExpansionStateManager.addQsExpansionListener { isQsExpanded -> - val changed = qsExpanded != isQsExpanded - qsExpanded = isQsExpanded - if (changed && !isQsExpanded) { - maybePerformPendingUnlock() - } - } + listenForQsExpandedChange(applicationScope) val dismissByDefault = if (context.resources.getBoolean( - com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0 + com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0 + tunerService.addTunable({ key, _ -> bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0 }, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD) + lockscreenUserManager.addUserChangedListener( - object : NotificationLockscreenUserManager.UserChangedListener { - override fun onUserChanged(userId: Int) { - pendingUnlock = null - } - }) + object : NotificationLockscreenUserManager.UserChangedListener { + override fun onUserChanged(userId: Int) { + pendingUnlock = null + } + }) } + @VisibleForTesting + fun listenForQsExpandedChange(scope: CoroutineScope) = + scope.launch { + shadeRepository.qsExpansion.map { it > 0f }.distinctUntilChanged() + .collect { isQsExpanded -> + val changed = qsExpanded != isQsExpanded + qsExpanded = isQsExpanded + if (changed && !isQsExpanded) { + maybePerformPendingUnlock() + } + } + } + private fun notifyListeners() = listeners.forEach { it.onBypassStateChanged(bypassEnabled) } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index 7efa705b7929..58126ae41a1d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -43,9 +43,9 @@ import android.widget.TextView; import androidx.annotation.VisibleForTesting; import com.android.settingslib.Utils; -import com.android.systemui.res.R; import com.android.systemui.battery.BatteryMeterView; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; +import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange; import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer; import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder; @@ -367,15 +367,22 @@ public class KeyguardStatusBarView extends RelativeLayout { mMultiUserAvatar.setImageDrawable(picture); } - /** Should only be called from {@link KeyguardStatusBarViewController}. */ - void onBatteryLevelChanged(boolean charging) { + /** + * Should only be called from {@link KeyguardStatusBarViewController} or + * {@link com.android.systemui.statusbar.ui.binder.KeyguardStatusBarViewBinder}. + */ + public void onBatteryChargingChanged(boolean charging) { if (mBatteryCharging != charging) { mBatteryCharging = charging; updateVisibilities(); } } - void setKeyguardUserSwitcherEnabled(boolean enabled) { + /** + * Should only be called from {@link KeyguardStatusBarViewController} or + * {@link com.android.systemui.statusbar.ui.binder.KeyguardStatusBarViewBinder}. + */ + public void setKeyguardUserSwitcherEnabled(boolean enabled) { mKeyguardUserSwitcherEnabled = enabled; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java index 9cf9714c274c..2960520f00b4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -172,7 +172,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat new BatteryController.BatteryStateChangeCallback() { @Override public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { - mView.onBatteryLevelChanged(charging); + mView.onBatteryChargingChanged(charging); } }; @@ -430,11 +430,18 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat /** Sets whether user switcher is enabled. */ public void setKeyguardUserSwitcherEnabled(boolean enabled) { + if (isMigrationEnabled()) { + return; + } mView.setKeyguardUserSwitcherEnabled(enabled); } /** Sets whether this controller should listen to battery updates. */ public void setBatteryListening(boolean listening) { + if (isMigrationEnabled()) { + return; + } + if (listening == mBatteryListening) { return; } @@ -472,6 +479,10 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat /** Animate the keyguard status bar in. */ public void animateKeyguardStatusBarIn() { + if (isMigrationEnabled()) { + return; + } + mLogger.log(TAG, LogLevel.DEBUG, "animating status bar in"); if (mDisableStateTracker.isDisabled()) { // If our view is disabled, don't allow us to animate in. @@ -488,6 +499,10 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat /** Animate the keyguard status bar out. */ public void animateKeyguardStatusBarOut(long startDelay, long duration) { + if (isMigrationEnabled()) { + return; + } + mLogger.log(TAG, LogLevel.DEBUG, "animating status bar out"); ValueAnimator anim = ValueAnimator.ofFloat(mView.getAlpha(), 0f); anim.addUpdateListener(mAnimatorUpdateListener); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java index 4bfce4cc173a..660aa3f641c4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java @@ -115,6 +115,7 @@ public class LegacyNotificationIconAreaControllerImpl implements private int mAodIconTint; private boolean mAodIconsVisible; private boolean mShowLowPriority = true; + private boolean mIsStatusViewMigrated = false; @VisibleForTesting final NotificationListener.NotificationSettingsListener mSettingsListener = @@ -158,7 +159,7 @@ public class LegacyNotificationIconAreaControllerImpl implements mStatusBarWindowController = statusBarWindowController; mScreenOffAnimationController = screenOffAnimationController; notificationListener.addNotificationSettingsListener(mSettingsListener); - + mIsStatusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW); initializeNotificationAreaViews(context); reloadAodColor(); darkIconDispatcher.addDarkReceiver(this); @@ -557,7 +558,9 @@ public class LegacyNotificationIconAreaControllerImpl implements return; } if (mScreenOffAnimationController.shouldAnimateAodIcons()) { - mAodIcons.setTranslationY(-mAodIconAppearTranslation); + if (!mIsStatusViewMigrated) { + mAodIcons.setTranslationY(-mAodIconAppearTranslation); + } mAodIcons.setAlpha(0); animateInAodIconTranslation(); mAodIcons.animate() @@ -567,16 +570,20 @@ public class LegacyNotificationIconAreaControllerImpl implements .start(); } else { mAodIcons.setAlpha(1.0f); - mAodIcons.setTranslationY(0); + if (!mIsStatusViewMigrated) { + mAodIcons.setTranslationY(0); + } } } private void animateInAodIconTranslation() { - mAodIcons.animate() - .setInterpolator(Interpolators.DECELERATE_QUINT) - .translationY(0) - .setDuration(AOD_ICONS_APPEAR_DURATION) - .start(); + if (!mIsStatusViewMigrated) { + mAodIcons.animate() + .setInterpolator(Interpolators.DECELERATE_QUINT) + .translationY(0) + .setDuration(AOD_ICONS_APPEAR_DURATION) + .start(); + } } private void reloadAodColor() { @@ -660,7 +667,9 @@ public class LegacyNotificationIconAreaControllerImpl implements } } else { mAodIcons.setAlpha(1.0f); - mAodIcons.setTranslationY(0); + if (!mIsStatusViewMigrated) { + mAodIcons.setTranslationY(0); + } mAodIcons.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/KeyguardStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/KeyguardStatusBarViewBinder.kt index c63ef9e5e012..6988e211855b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/KeyguardStatusBarViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/KeyguardStatusBarViewBinder.kt @@ -22,6 +22,8 @@ import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.statusbar.phone.KeyguardStatusBarView import com.android.systemui.statusbar.ui.viewmodel.KeyguardStatusBarViewModel +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.launch /** Binds [KeyguardStatusBarViewModel] to [KeyguardStatusBarView]. */ object KeyguardStatusBarViewBinder { @@ -32,8 +34,18 @@ object KeyguardStatusBarViewBinder { ) { view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.isVisible.collect { isVisible -> - view.visibility = if (isVisible) View.VISIBLE else View.INVISIBLE + launch { + viewModel.isVisible.collect { isVisible -> + view.visibility = if (isVisible) View.VISIBLE else View.INVISIBLE + } + } + + launch { viewModel.isBatteryCharging.collect { view.onBatteryChargingChanged(it) } } + + launch { + viewModel.isKeyguardUserSwitcherEnabled.distinctUntilChanged().collect { + view.setKeyguardUserSwitcherEnabled(it) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt index ddfed8795fcf..5da01e23e268 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt @@ -16,12 +16,18 @@ package com.android.systemui.statusbar.ui.viewmodel +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor +import com.android.systemui.statusbar.policy.BatteryController +import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine @@ -41,6 +47,8 @@ class KeyguardStatusBarViewModel constructor( @Application scope: CoroutineScope, keyguardInteractor: KeyguardInteractor, + keyguardStatusBarInteractor: KeyguardStatusBarInteractor, + batteryController: BatteryController, ) { /** True if this view should be visible and false otherwise. */ val isVisible: StateFlow<Boolean> = @@ -51,4 +59,26 @@ constructor( !isDozing && statusBarState == StatusBarState.KEYGUARD } .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + /** True if the device's battery is currently charging and false otherwise. */ + // Note: Never make this an eagerly-started state flow so that the callback is removed when the + // keyguard status bar view isn't attached. + val isBatteryCharging: Flow<Boolean> = conflatedCallbackFlow { + val callback = + object : BatteryStateChangeCallback { + override fun onBatteryLevelChanged( + level: Int, + pluggedIn: Boolean, + charging: Boolean, + ) { + trySend(charging) + } + } + batteryController.addCallback(callback) + awaitClose { batteryController.removeCallback(callback) } + } + + /** True if we can show the user switcher on keyguard and false otherwise. */ + val isKeyguardUserSwitcherEnabled: Flow<Boolean> = + keyguardStatusBarInteractor.isKeyguardUserSwitcherEnabled } diff --git a/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt b/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt index 9c38dc0f8852..3b300249aac2 100644 --- a/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt @@ -17,10 +17,13 @@ package com.android.systemui.telephony.data.repository +import android.content.Context +import android.content.pm.PackageManager import android.telephony.Annotation import android.telephony.TelephonyCallback import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.telephony.TelephonyListenerManager import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose @@ -30,6 +33,9 @@ import kotlinx.coroutines.flow.Flow interface TelephonyRepository { /** The state of the current call. */ @Annotation.CallState val callState: Flow<Int> + + /** Whether the device has a radio that can be used for telephony. */ + val hasTelephonyRadio: Boolean } /** @@ -43,6 +49,7 @@ interface TelephonyRepository { class TelephonyRepositoryImpl @Inject constructor( + @Application private val applicationContext: Context, private val manager: TelephonyListenerManager, ) : TelephonyRepository { @Annotation.CallState @@ -53,4 +60,7 @@ constructor( awaitClose { manager.removeCallStateListener(listener) } } + + override val hasTelephonyRadio: Boolean + get() = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) } diff --git a/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt b/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt index 86ca33df24dd..4642f552615a 100644 --- a/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt @@ -28,7 +28,11 @@ import kotlinx.coroutines.flow.Flow class TelephonyInteractor @Inject constructor( - repository: TelephonyRepository, + private val repository: TelephonyRepository, ) { @Annotation.CallState val callState: Flow<Int> = repository.callState + + /** Whether the device has a radio that can be used for telephony. */ + val hasTelephonyRadio: Boolean + get() = repository.hasTelephonyRadio } diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepositoryModule.kt index 18ae1070e1bb..71352eff026c 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepositoryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepositoryModule.kt @@ -23,4 +23,6 @@ import dagger.Module @Module interface UserRepositoryModule { @Binds fun bindRepository(impl: UserRepositoryImpl): UserRepository + + @Binds fun userSwitcherRepository(impl: UserSwitcherRepositoryImpl): UserSwitcherRepository } diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/UserSwitcherRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt index 5fa75ad68165..dc7fadd5eb14 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/UserSwitcherRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.footer.data.repository +package com.android.systemui.user.data.repository import android.content.Context import android.graphics.drawable.Drawable @@ -22,7 +22,6 @@ import android.os.Handler import android.os.UserManager import android.provider.Settings.Global.USER_SWITCHER_ENABLED import com.android.keyguard.KeyguardUpdateMonitor -import com.android.systemui.res.R import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton @@ -30,6 +29,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.qs.SettingObserver import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel +import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.UserInfoController import com.android.systemui.statusbar.policy.UserSwitcherController @@ -48,6 +48,9 @@ import kotlinx.coroutines.withContext interface UserSwitcherRepository { /** The current [UserSwitcherStatusModel]. */ val userSwitcherStatus: Flow<UserSwitcherStatusModel> + + /** Whether the user switcher is currently enabled. */ + val isEnabled: Flow<Boolean> } @SysUISingleton @@ -66,8 +69,7 @@ constructor( private val showUserSwitcherForSingleUser = context.resources.getBoolean(R.bool.qs_show_user_switcher_for_single_user) - /** Whether the user switcher is currently enabled. */ - private val isEnabled: Flow<Boolean> = conflatedCallbackFlow { + override val isEnabled: Flow<Boolean> = conflatedCallbackFlow { suspend fun updateState() { trySendWithFailureLogging(isUserSwitcherEnabled(), TAG) } diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt index 61952baba7b1..d3f83b1e18bd 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt @@ -17,10 +17,10 @@ package com.android.systemui.user.ui.viewmodel -import com.android.systemui.res.R import com.android.systemui.common.shared.model.Text import com.android.systemui.common.ui.drawable.CircularDrawable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.res.R import com.android.systemui.user.domain.interactor.GuestUserInteractor import com.android.systemui.user.domain.interactor.UserInteractor import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper @@ -42,6 +42,10 @@ constructor( private val guestUserInteractor: GuestUserInteractor, ) { + /** The currently selected user. */ + val selectedUser: Flow<UserViewModel> = + userInteractor.selectedUser.map { user -> toViewModel(user) } + /** On-device users. */ val users: Flow<List<UserViewModel>> = userInteractor.users.map { models -> models.map { user -> toViewModel(user) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt index 6b5679a2a92a..8e54eb7334dd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt @@ -301,7 +301,10 @@ class AuthRippleControllerTest : SysuiTestCase() { } @Test - fun testUdfps_onFingerDown_showDwellRipple() { + fun testUdfps_onFingerDown_runningForDeviceEntry_showDwellRipple() { + // GIVEN fingerprint detection is running on keyguard + `when`(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(true) + // GIVEN view is already attached controller.onViewAttached() val captor = ArgumentCaptor.forClass(UdfpsController.Callback::class.java) @@ -318,4 +321,21 @@ class AuthRippleControllerTest : SysuiTestCase() { verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f) verify(rippleView).startDwellRipple(false) } + + @Test + fun testUdfps_onFingerDown_notDeviceEntry_doesNotShowDwellRipple() { + // GIVEN fingerprint detection is NOT running on keyguard + `when`(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(false) + + // GIVEN view is already attached + controller.onViewAttached() + val captor = ArgumentCaptor.forClass(UdfpsController.Callback::class.java) + verify(udfpsController).addCallback(captor.capture()) + + // WHEN finger is down + captor.value.onFingerDown() + + // THEN doesn't show dwell ripple + verify(rippleView, never()).startDwellRipple(false) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt index 630826954057..5eab2fc73fe6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt @@ -24,6 +24,8 @@ import com.android.systemui.common.ui.data.repository.FakeConfigurationRepositor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.doze.util.BurnInHelperWrapper import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.shared.model.BurnInModel +import com.android.systemui.res.R import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -56,6 +58,11 @@ class BurnInInteractorTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) configurationRepository = FakeConfigurationRepository() + + context + .getOrCreateTestableResources() + .addOverride(R.dimen.burn_in_prevention_offset_y, burnInOffset) + KeyguardInteractorFactory.create().let { keyguardInteractor = it.keyguardInteractor fakeKeyguardRepository = it.repository @@ -110,6 +117,25 @@ class BurnInInteractorTest : SysuiTestCase() { assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress) } + @Test + fun keyguardBurnIn() = + testScope.runTest { + whenever(burnInHelperWrapper.burnInScale()).thenReturn(0.5f) + + val burnInModel by collectLastValue(underTest.keyguardBurnIn) + + // After time tick, returns the configured values + fakeKeyguardRepository.dozeTimeTick(10) + assertThat(burnInModel) + .isEqualTo( + BurnInModel( + translationX = burnInOffset.toInt(), + translationY = burnInOffset.toInt(), + scale = 0.5f, + ) + ) + } + private fun setBurnInProgress(progress: Float) { burnInProgress = progress whenever(burnInHelperWrapper.burnInProgressOffset()).thenReturn(burnInProgress) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt index 7f4755df160b..7940b450b932 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.shared.model.KeyguardBlueprint import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.view.KeyguardRootView +import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection @@ -63,6 +64,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { @Mock private lateinit var defaultNSSLSection: DefaultNotificationStackScrollLayoutSection @Mock private lateinit var splitShadeGuidelines: SplitShadeGuidelines @Mock private lateinit var aodNotificationIconsSection: AodNotificationIconsSection + @Mock private lateinit var aodBurnInSection: AodBurnInSection @Before fun setup() { @@ -80,6 +82,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { defaultNSSLSection, splitShadeGuidelines, aodNotificationIconsSection, + aodBurnInSection, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index a14a1c536f87..71688db76a8b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -23,17 +23,23 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory -import com.google.common.truth.Truth +import com.android.systemui.keyguard.shared.model.BurnInModel +import com.android.systemui.plugins.ClockController +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import javax.inject.Provider +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest -import org.junit.Assert.* import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 +import org.mockito.Answers import org.mockito.Mock import org.mockito.MockitoAnnotations @@ -45,12 +51,16 @@ class KeyguardRootViewModelTest : SysuiTestCase() { private lateinit var testScope: TestScope private lateinit var repository: FakeKeyguardRepository private lateinit var keyguardInteractor: KeyguardInteractor + @Mock private lateinit var burnInInteractor: BurnInInteractor + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var clockController: ClockController + + private val burnInFlow = MutableStateFlow(BurnInModel()) @Before fun setUp() { - MockitoAnnotations.initMocks(this) val testDispatcher = StandardTestDispatcher() testScope = TestScope(testDispatcher) + MockitoAnnotations.initMocks(this) val featureFlags = FakeFeatureFlags().apply { @@ -62,7 +72,9 @@ class KeyguardRootViewModelTest : SysuiTestCase() { keyguardInteractor = withDeps.keyguardInteractor repository = withDeps.repository - underTest = KeyguardRootViewModel(keyguardInteractor) + whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow) + underTest = KeyguardRootViewModel(keyguardInteractor, burnInInteractor) + underTest.clockControllerProvider = Provider { clockController } } @Test @@ -70,15 +82,15 @@ class KeyguardRootViewModelTest : SysuiTestCase() { testScope.runTest { val value = collectLastValue(underTest.alpha) - Truth.assertThat(value()).isEqualTo(1f) + assertThat(value()).isEqualTo(1f) repository.setKeyguardAlpha(0.1f) - Truth.assertThat(value()).isEqualTo(0.1f) + assertThat(value()).isEqualTo(0.1f) repository.setKeyguardAlpha(0.5f) - Truth.assertThat(value()).isEqualTo(0.5f) + assertThat(value()).isEqualTo(0.5f) repository.setKeyguardAlpha(0.2f) - Truth.assertThat(value()).isEqualTo(0.2f) + assertThat(value()).isEqualTo(0.2f) repository.setKeyguardAlpha(0f) - Truth.assertThat(value()).isEqualTo(0f) + assertThat(value()).isEqualTo(0f) } @Test @@ -87,14 +99,85 @@ class KeyguardRootViewModelTest : SysuiTestCase() { val value = collectLastValue(underTest.alpha) underTest.enablePreviewMode() - Truth.assertThat(value()).isEqualTo(1f) + assertThat(value()).isEqualTo(1f) repository.setKeyguardAlpha(0.1f) - Truth.assertThat(value()).isEqualTo(1f) + assertThat(value()).isEqualTo(1f) repository.setKeyguardAlpha(0.5f) - Truth.assertThat(value()).isEqualTo(1f) + assertThat(value()).isEqualTo(1f) repository.setKeyguardAlpha(0.2f) - Truth.assertThat(value()).isEqualTo(1f) + assertThat(value()).isEqualTo(1f) repository.setKeyguardAlpha(0f) - Truth.assertThat(value()).isEqualTo(1f) + assertThat(value()).isEqualTo(1f) + } + + @Test + fun translationAndScaleFromBurnInNotDozing() = + testScope.runTest { + val translationX by collectLastValue(underTest.translationX) + val translationY by collectLastValue(underTest.translationY) + val scale by collectLastValue(underTest.scale) + + // Set to not dozing (on lockscreen) + repository.setDozeAmount(0f) + + // Trigger a change to the burn-in model + burnInFlow.value = + BurnInModel( + translationX = 20, + translationY = 30, + scale = 0.5f, + ) + + assertThat(translationX).isEqualTo(0) + assertThat(translationY).isEqualTo(0) + assertThat(scale).isEqualTo(Pair(1f, true /* scaleClockOnly */)) + } + + @Test + fun translationAndScaleFromBurnFullyDozing() = + testScope.runTest { + val translationX by collectLastValue(underTest.translationX) + val translationY by collectLastValue(underTest.translationY) + val scale by collectLastValue(underTest.scale) + + // Set to dozing (on AOD) + repository.setDozeAmount(1f) + + // Trigger a change to the burn-in model + burnInFlow.value = + BurnInModel( + translationX = 20, + translationY = 30, + scale = 0.5f, + ) + + assertThat(translationX).isEqualTo(20) + assertThat(translationY).isEqualTo(30) + assertThat(scale).isEqualTo(Pair(0.5f, true /* scaleClockOnly */)) + } + + @Test + fun translationAndScaleFromBurnInUseScaleOnly() = + testScope.runTest { + whenever(clockController.config.useAlternateSmartspaceAODTransition).thenReturn(true) + + val translationX by collectLastValue(underTest.translationX) + val translationY by collectLastValue(underTest.translationY) + val scale by collectLastValue(underTest.scale) + + // Set to dozing (on AOD) + repository.setDozeAmount(1f) + + // Trigger a change to the burn-in model + burnInFlow.value = + BurnInModel( + translationX = 20, + translationY = 30, + scale = 0.5f, + ) + + assertThat(translationX).isEqualTo(0) + assertThat(translationY).isEqualTo(0) + assertThat(scale).isEqualTo(Pair(0.5f, false /* scaleClockOnly */)) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt new file mode 100644 index 000000000000..2c4e10eadfc9 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.base.analytics + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.internal.logging.InstanceId +import com.android.internal.logging.UiEventLogger +import com.android.systemui.RoboPilotTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.QSEvent +import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.eq +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RoboPilotTest +@RunWith(AndroidJUnit4::class) +class QSTileAnalyticsTest : SysuiTestCase() { + + @Mock private lateinit var uiEventLogger: UiEventLogger + + private lateinit var underTest: QSTileAnalytics + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + underTest = QSTileAnalytics(uiEventLogger) + } + + @Test + fun testClickIsLogged() { + underTest.trackUserAction(config, QSTileUserAction.Click(null)) + + verify(uiEventLogger) + .logWithInstanceId( + eq(QSEvent.QS_ACTION_CLICK), + eq(0), + eq("test_spec"), + eq(InstanceId.fakeInstanceId(0)) + ) + } + + @Test + fun testLongClickIsLogged() { + underTest.trackUserAction(config, QSTileUserAction.LongClick(null)) + + verify(uiEventLogger) + .logWithInstanceId( + eq(QSEvent.QS_ACTION_LONG_PRESS), + eq(0), + eq("test_spec"), + eq(InstanceId.fakeInstanceId(0)) + ) + } + + private companion object { + + val config = QSTileConfigTestBuilder.build() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt new file mode 100644 index 000000000000..4401e0d60da6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.base.logging + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.RoboPilotTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dump.LogcatEchoTrackerAlways +import com.android.systemui.log.LogBuffer +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import java.io.StringWriter +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@SmallTest +@RoboPilotTest +@RunWith(AndroidJUnit4::class) +class QSTileLoggerTest : SysuiTestCase() { + + @Mock private lateinit var statusBarController: StatusBarStateController + + private val chattyLogBuffer = LogBuffer("TestChatty", 5, LogcatEchoTrackerAlways()) + private val logBuffer = LogBuffer("Test", 1, LogcatEchoTrackerAlways()) + + private lateinit var underTest: QSTileLogger + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + underTest = + QSTileLogger( + mapOf(TileSpec.create("chatty_tile") to chattyLogBuffer), + { logBuffer }, + statusBarController + ) + } + + @Test + fun testChattyLog() { + underTest.logUserActionRejectedByFalsing( + QSTileUserAction.Click(null), + TileSpec.create("chatty_tile"), + ) + underTest.logUserActionRejectedByFalsing( + QSTileUserAction.Click(null), + TileSpec.create("chatty_tile"), + ) + + val logs = chattyLogBuffer.getStringBuffer().lines().filter { it.isNotBlank() } + assertThat(logs).hasSize(2) + logs.forEach { assertThat(it).contains("tile click: rejected by falsing") } + } + + @Test + fun testLogUserAction() { + underTest.logUserAction( + QSTileUserAction.Click(null), + TileSpec.create("test_spec"), + hasData = false, + hasTileState = false, + ) + + assertThat(logBuffer.getStringBuffer()) + .contains("tile click: statusBarState=SHADE, hasState=false, hasData=false") + } + + @Test + fun testLogUserActionRejectedByFalsing() { + underTest.logUserActionRejectedByFalsing( + QSTileUserAction.Click(null), + TileSpec.create("test_spec"), + ) + + assertThat(logBuffer.getStringBuffer()).contains("tile click: rejected by falsing") + } + + @Test + fun testLogUserActionRejectedByPolicy() { + underTest.logUserActionRejectedByPolicy( + QSTileUserAction.Click(null), + TileSpec.create("test_spec"), + ) + + assertThat(logBuffer.getStringBuffer()).contains("tile click: rejected by policy") + } + + @Test + fun testLogUserActionPipeline() { + underTest.logUserActionPipeline( + TileSpec.create("test_spec"), + QSTileUserAction.Click(null), + QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {}, + "test_data", + ) + + assertThat(logBuffer.getStringBuffer()) + .contains( + "tile click pipeline: " + + "statusBarState=SHADE, " + + "state=[" + + "label=, " + + "state=INACTIVE, " + + "s_label=null, " + + "cd=null, " + + "sd=null, " + + "svi=None, " + + "enabled=ENABLED, " + + "a11y=null" + + "], " + + "data=test_data" + ) + } + + @Test + fun testLogStateUpdate() { + underTest.logStateUpdate( + TileSpec.create("test_spec"), + StateUpdateTrigger.ForceUpdate, + QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {}, + "test_data", + ) + + assertThat(logBuffer.getStringBuffer()) + .contains( + "tile state update: " + + "trigger=force, " + + "state=[" + + "label=, " + + "state=INACTIVE, " + + "s_label=null, " + + "cd=null, " + + "sd=null, " + + "svi=None, " + + "enabled=ENABLED, " + + "a11y=null" + + "], " + + "data=test_data" + ) + } + + private fun LogBuffer.getStringBuffer(): String { + val stringWriter = StringWriter() + dump(PrintWriter(stringWriter), 0) + return stringWriter.buffer.toString() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt index 89fa55b319ba..8b6604048ea8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt @@ -29,8 +29,6 @@ import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.systemui.SysuiTestCase -import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.DISABLED_ALPHA -import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.ENABLED_ALPHA import com.android.systemui.res.R import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -62,6 +60,8 @@ class BluetoothTileDialogTest : SysuiTestCase() { @Mock private lateinit var uiEventLogger: UiEventLogger + private val subtitleResId = R.string.quick_settings_bluetooth_tile_subtitle + private lateinit var icon: Pair<Drawable, String> private lateinit var bluetoothTileDialog: BluetoothTileDialog private lateinit var deviceItem: DeviceItem @@ -69,7 +69,13 @@ class BluetoothTileDialogTest : SysuiTestCase() { @Before fun setUp() { bluetoothTileDialog = - BluetoothTileDialog(ENABLED, bluetoothTileDialogCallback, uiEventLogger, mContext) + BluetoothTileDialog( + ENABLED, + subtitleResId, + bluetoothTileDialogCallback, + uiEventLogger, + mContext + ) icon = Pair(drawable, DEVICE_NAME) deviceItem = DeviceItem( @@ -99,7 +105,13 @@ class BluetoothTileDialogTest : SysuiTestCase() { @Test fun testShowDialog_displayBluetoothDevice() { bluetoothTileDialog = - BluetoothTileDialog(ENABLED, bluetoothTileDialogCallback, uiEventLogger, mContext) + BluetoothTileDialog( + ENABLED, + subtitleResId, + bluetoothTileDialogCallback, + uiEventLogger, + mContext + ) bluetoothTileDialog.show() bluetoothTileDialog.onDeviceItemUpdated( listOf(deviceItem), @@ -118,49 +130,61 @@ class BluetoothTileDialogTest : SysuiTestCase() { @Test fun testDeviceItemViewHolder_cachedDeviceNotBusy() { deviceItem.isEnabled = true - deviceItem.alpha = ENABLED_ALPHA val view = LayoutInflater.from(mContext).inflate(R.layout.bluetooth_device_item, null, false) val viewHolder = - BluetoothTileDialog(ENABLED, bluetoothTileDialogCallback, uiEventLogger, mContext) + BluetoothTileDialog( + ENABLED, + subtitleResId, + bluetoothTileDialogCallback, + uiEventLogger, + mContext + ) .Adapter(bluetoothTileDialogCallback) .DeviceItemViewHolder(view) - viewHolder.bind(deviceItem, 0, bluetoothTileDialogCallback) - val container = view.requireViewById<View>(R.id.bluetooth_device) - val deviceView = view.requireViewById<View>(R.id.bluetooth_device) + viewHolder.bind(deviceItem, bluetoothTileDialogCallback) + val container = view.requireViewById<View>(R.id.bluetooth_device_row) assertThat(container).isNotNull() assertThat(container.isEnabled).isTrue() - assertThat(container.alpha).isEqualTo(ENABLED_ALPHA) - assertThat(deviceView.hasOnClickListeners()).isTrue() + assertThat(container.hasOnClickListeners()).isTrue() } @Test fun testDeviceItemViewHolder_cachedDeviceBusy() { deviceItem.isEnabled = false - deviceItem.alpha = DISABLED_ALPHA val view = LayoutInflater.from(mContext).inflate(R.layout.bluetooth_device_item, null, false) val viewHolder = - BluetoothTileDialog(ENABLED, bluetoothTileDialogCallback, uiEventLogger, mContext) + BluetoothTileDialog( + ENABLED, + subtitleResId, + bluetoothTileDialogCallback, + uiEventLogger, + mContext + ) .Adapter(bluetoothTileDialogCallback) .DeviceItemViewHolder(view) - viewHolder.bind(deviceItem, 0, bluetoothTileDialogCallback) + viewHolder.bind(deviceItem, bluetoothTileDialogCallback) val container = view.requireViewById<View>(R.id.bluetooth_device_row) - val deviceView = view.requireViewById<View>(R.id.bluetooth_device) assertThat(container).isNotNull() assertThat(container.isEnabled).isFalse() - assertThat(container.alpha).isEqualTo(DISABLED_ALPHA) - assertThat(deviceView.hasOnClickListeners()).isTrue() + assertThat(container.hasOnClickListeners()).isTrue() } @Test fun testOnDeviceUpdated_hideSeeAll_showPairNew() { bluetoothTileDialog = - BluetoothTileDialog(ENABLED, bluetoothTileDialogCallback, uiEventLogger, mContext) + BluetoothTileDialog( + ENABLED, + subtitleResId, + bluetoothTileDialogCallback, + uiEventLogger, + mContext + ) bluetoothTileDialog.show() bluetoothTileDialog.onDeviceItemUpdated( listOf(deviceItem), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt index 7157cce8e607..a0ff2ab330b8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt @@ -95,10 +95,11 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { testScope.backgroundScope, dispatcher, ) - `when`(deviceItemInteractor.deviceItemFlow).thenReturn(MutableStateFlow(null).asStateFlow()) - `when`(bluetoothStateInteractor.updateBluetoothStateFlow) + `when`(deviceItemInteractor.deviceItemUpdate) .thenReturn(MutableStateFlow(null).asStateFlow()) - `when`(deviceItemInteractor.updateDeviceItemsFlow) + `when`(bluetoothStateInteractor.bluetoothStateUpdate) + .thenReturn(MutableStateFlow(null).asStateFlow()) + `when`(deviceItemInteractor.deviceItemUpdateRequest) .thenReturn(MutableStateFlow(Unit).asStateFlow()) `when`(bluetoothStateInteractor.isBluetoothEnabled).thenReturn(true) } @@ -143,7 +144,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { bluetoothTileDialogViewModel.showDialog(context, null) assertThat(bluetoothTileDialogViewModel.dialog).isNotNull() - verify(deviceItemInteractor).deviceItemFlow + verify(deviceItemInteractor).deviceItemUpdate } } @@ -153,7 +154,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { bluetoothTileDialogViewModel.showDialog(context, null) assertThat(bluetoothTileDialogViewModel.dialog).isNotNull() - verify(bluetoothStateInteractor).updateBluetoothStateFlow + verify(bluetoothStateInteractor).bluetoothStateUpdate } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt index 34519023e316..92c73261a95e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt @@ -69,7 +69,7 @@ class DeviceItemFactoryTest : SysuiTestCase() { val deviceItem = savedDeviceItemFactory.create(context, cachedDevice) assertDeviceItem(deviceItem, DeviceItemType.SAVED_BLUETOOTH_DEVICE) - assertThat(deviceItem.background).isNull() + assertThat(deviceItem.background).isNotNull() } private fun assertDeviceItem(deviceItem: DeviceItem?, deviceItemType: DeviceItemType) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt index 07a95ae330c6..3593075c70fd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt @@ -109,7 +109,7 @@ class DeviceItemInteractorTest : SysuiTestCase() { interactor.updateDeviceItems(mContext) - assertThat(interactor.deviceItemFlow.value).isEmpty() + assertThat(interactor.deviceItemUpdate.value).isEmpty() } } @@ -123,7 +123,7 @@ class DeviceItemInteractorTest : SysuiTestCase() { interactor.updateDeviceItems(mContext) - assertThat(interactor.deviceItemFlow.value).isEmpty() + assertThat(interactor.deviceItemUpdate.value).isEmpty() } } @@ -137,8 +137,8 @@ class DeviceItemInteractorTest : SysuiTestCase() { interactor.updateDeviceItems(mContext) - assertThat(interactor.deviceItemFlow.value).hasSize(1) - assertThat(interactor.deviceItemFlow.value!![0]).isEqualTo(deviceItem1) + assertThat(interactor.deviceItemUpdate.value).hasSize(1) + assertThat(interactor.deviceItemUpdate.value!![0]).isEqualTo(deviceItem1) } } @@ -152,9 +152,9 @@ class DeviceItemInteractorTest : SysuiTestCase() { interactor.updateDeviceItems(mContext) - assertThat(interactor.deviceItemFlow.value).hasSize(2) - assertThat(interactor.deviceItemFlow.value!![0]).isEqualTo(deviceItem2) - assertThat(interactor.deviceItemFlow.value!![1]).isEqualTo(deviceItem2) + assertThat(interactor.deviceItemUpdate.value).hasSize(2) + assertThat(interactor.deviceItemUpdate.value!![0]).isEqualTo(deviceItem2) + assertThat(interactor.deviceItemUpdate.value!![1]).isEqualTo(deviceItem2) } } @@ -179,7 +179,8 @@ class DeviceItemInteractorTest : SysuiTestCase() { interactor.updateDeviceItems(mContext) - assertThat(interactor.deviceItemFlow.value).isEqualTo(listOf(deviceItem2, deviceItem1)) + assertThat(interactor.deviceItemUpdate.value) + .isEqualTo(listOf(deviceItem2, deviceItem1)) } } @@ -201,7 +202,8 @@ class DeviceItemInteractorTest : SysuiTestCase() { interactor.updateDeviceItems(mContext) - assertThat(interactor.deviceItemFlow.value).isEqualTo(listOf(deviceItem2, deviceItem1)) + assertThat(interactor.deviceItemUpdate.value) + .isEqualTo(listOf(deviceItem2, deviceItem1)) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt index 9024c6c5576b..4760dfa3561d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt @@ -1,21 +1,39 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.qs.tiles.viewmodel -import android.graphics.drawable.ShapeDrawable import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import com.android.internal.logging.InstanceId import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger +import com.android.systemui.qs.tiles.base.logging.QSTileLogger import com.android.systemui.qs.tiles.base.viewmodel.BaseQSTileViewModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.launchIn @@ -26,6 +44,8 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations // TODO(b/299909368): Add more tests @MediumTest @@ -34,9 +54,13 @@ import org.junit.runner.RunWith @TestableLooper.RunWithLooper(setAsMainLooper = true) class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() { + @Mock private lateinit var qsTileLogger: QSTileLogger + @Mock private lateinit var qsTileAnalytics: QSTileAnalytics + private val fakeQSTileDataInteractor = FakeQSTileDataInteractor<Any>() private val fakeQSTileUserActionInteractor = FakeQSTileUserActionInteractor<Any>() private val fakeDisabledByPolicyInteractor = FakeDisabledByPolicyInteractor() + private val fakeFalsingManager = FalsingManagerFake() private val testCoroutineDispatcher = StandardTestDispatcher() private val testScope = TestScope(testCoroutineDispatcher) @@ -45,6 +69,7 @@ class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() { @Before fun setup() { + MockitoAnnotations.initMocks(this) underTest = createViewModel(testScope) } @@ -79,6 +104,9 @@ class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() { QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {} }, fakeDisabledByPolicyInteractor, + fakeFalsingManager, + qsTileAnalytics, + qsTileLogger, testCoroutineDispatcher, scope.backgroundScope, ) @@ -88,7 +116,7 @@ class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() { val TEST_QS_TILE_CONFIG = QSTileConfig( TileSpec.create("default"), - Icon.Loaded(ShapeDrawable(), null), + Icon.Resource(0, null), 0, InstanceId.fakeInstanceId(0), ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeKeyguardStatusBarRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeKeyguardStatusBarRepository.kt new file mode 100644 index 000000000000..f1e6a053643f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeKeyguardStatusBarRepository.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.data.repository + +import kotlinx.coroutines.flow.MutableStateFlow + +class FakeKeyguardStatusBarRepository : KeyguardStatusBarRepository { + override val isKeyguardUserSwitcherEnabled = MutableStateFlow(false) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryImplTest.kt new file mode 100644 index 000000000000..b1c994c2374e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryImplTest.kt @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.data.repository + +import androidx.test.filters.SmallTest +import com.android.internal.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.user.data.repository.FakeUserSwitcherRepository +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.mockito.Mockito.verify + +@SmallTest +class KeyguardStatusBarRepositoryImplTest : SysuiTestCase() { + private val testScope = TestScope() + private val configurationController = mock<ConfigurationController>() + private val userSwitcherRepository = FakeUserSwitcherRepository() + + val underTest = + KeyguardStatusBarRepositoryImpl( + context, + configurationController, + userSwitcherRepository, + ) + + private val configurationListener: ConfigurationController.ConfigurationListener + get() { + val captor = argumentCaptor<ConfigurationController.ConfigurationListener>() + verify(configurationController).addCallback(capture(captor)) + return captor.value + } + + @Test + fun isKeyguardUserSwitcherEnabled_switcherNotEnabled_false() = + testScope.runTest { + val latest by collectLastValue(underTest.isKeyguardUserSwitcherEnabled) + + userSwitcherRepository.isEnabled.value = false + + assertThat(latest).isFalse() + } + + @Test + fun isKeyguardUserSwitcherEnabled_keyguardConfigNotEnabled_false() = + testScope.runTest { + val latest by collectLastValue(underTest.isKeyguardUserSwitcherEnabled) + userSwitcherRepository.isEnabled.value = true + + context.orCreateTestableResources.addOverride(R.bool.config_keyguardUserSwitcher, false) + + assertThat(latest).isFalse() + } + + @Test + fun isKeyguardUserSwitcherEnabled_switchEnabledAndKeyguardConfigEnabled_true() = + testScope.runTest { + val latest by collectLastValue(underTest.isKeyguardUserSwitcherEnabled) + + userSwitcherRepository.isEnabled.value = true + context.orCreateTestableResources.addOverride(R.bool.config_keyguardUserSwitcher, true) + + assertThat(latest).isTrue() + } + + @Test + fun isKeyguardUserSwitcherEnabled_refetchedOnSmallestWidthChanged() = + testScope.runTest { + val latest by collectLastValue(underTest.isKeyguardUserSwitcherEnabled) + userSwitcherRepository.isEnabled.value = true + context.orCreateTestableResources.addOverride(R.bool.config_keyguardUserSwitcher, true) + assertThat(latest).isTrue() + + context.orCreateTestableResources.addOverride(R.bool.config_keyguardUserSwitcher, false) + configurationListener.onSmallestScreenWidthChanged() + + assertThat(latest).isFalse() + } + + @Test + fun isKeyguardUserSwitcherEnabled_refetchedOnDensityChanged() = + testScope.runTest { + val latest by collectLastValue(underTest.isKeyguardUserSwitcherEnabled) + userSwitcherRepository.isEnabled.value = true + context.orCreateTestableResources.addOverride(R.bool.config_keyguardUserSwitcher, true) + assertThat(latest).isTrue() + + context.orCreateTestableResources.addOverride(R.bool.config_keyguardUserSwitcher, false) + configurationListener.onDensityOrFontScaleChanged() + + assertThat(latest).isFalse() + } + + @Test + fun isKeyguardUserSwitcherEnabled_refetchedOnEnabledChanged() = + testScope.runTest { + val latest by collectLastValue(underTest.isKeyguardUserSwitcherEnabled) + + userSwitcherRepository.isEnabled.value = false + context.orCreateTestableResources.addOverride(R.bool.config_keyguardUserSwitcher, true) + assertThat(latest).isFalse() + + // WHEN the switcher becomes enabled but the keyguard switcher becomes disabled + context.orCreateTestableResources.addOverride(R.bool.config_keyguardUserSwitcher, false) + userSwitcherRepository.isEnabled.value = true + + // THEN the value is still false because the keyguard config is refetched + assertThat(latest).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt index 126e0e82b210..7caa5ccc5837 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt @@ -64,6 +64,7 @@ class NotificationIconAreaControllerViewBinderWrapperImplTest : SysuiTestCase() featureFlags = FakeFeatureFlagsClassicModule { set(Flags.FACE_AUTH_REFACTOR, value = false) + set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, value = false) }, mocks = TestMocksModule( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt index ab441e39e862..6209f73f2e92 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt @@ -20,11 +20,14 @@ import android.content.pm.PackageManager import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import android.testing.TestableLooper -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.shade.ShadeExpansionStateManager +import com.android.systemui.res.R +import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED @@ -34,6 +37,9 @@ import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POST import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.tuner.TunerService import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Before import org.junit.Rule @@ -54,6 +60,10 @@ import org.mockito.junit.MockitoJUnit @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper class KeyguardBypassControllerTest : SysuiTestCase() { + private val utils = SceneTestUtils(this) + private val testScope = utils.testScope + private val featureFlags = FakeFeatureFlags() + private val shadeRepository = FakeShadeRepository() private lateinit var keyguardBypassController: KeyguardBypassController private lateinit var postureControllerCallback: DevicePostureController.Callback @@ -61,10 +71,10 @@ class KeyguardBypassControllerTest : SysuiTestCase() { @Mock private lateinit var statusBarStateController: StatusBarStateController @Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager @Mock private lateinit var keyguardStateController: KeyguardStateController - @Mock private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager @Mock private lateinit var devicePostureController: DevicePostureController @Mock private lateinit var dumpManager: DumpManager @Mock private lateinit var packageManager: PackageManager + @Captor private val postureCallbackCaptor = ArgumentCaptor.forClass(DevicePostureController.Callback::class.java) @@ -73,6 +83,8 @@ class KeyguardBypassControllerTest : SysuiTestCase() { @Before fun setUp() { context.setMockPackageManager(packageManager) + featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) + whenever(packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true) whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true) } @@ -126,11 +138,12 @@ class KeyguardBypassControllerTest : SysuiTestCase() { keyguardBypassController = KeyguardBypassController( context, + testScope.backgroundScope, tunerService, statusBarStateController, lockscreenUserManager, keyguardStateController, - shadeExpansionStateManager, + shadeRepository, devicePostureController, dumpManager ) @@ -267,4 +280,25 @@ class KeyguardBypassControllerTest : SysuiTestCase() { assertThat(keyguardBypassController.bypassEnabled).isFalse() } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun qsExpansion_updates() { + testScope.runTest { + initKeyguardBypassController() + assertThat(keyguardBypassController.qsExpanded).isFalse() + val job = keyguardBypassController.listenForQsExpandedChange(this) + shadeRepository.setQsExpansion(0.5f) + runCurrent() + + assertThat(keyguardBypassController.qsExpanded).isTrue() + + shadeRepository.setQsExpansion(0f) + runCurrent() + + assertThat(keyguardBypassController.qsExpanded).isFalse() + + job.cancel() + } + } } 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 c0d248ea15e7..648438997166 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 @@ -64,12 +64,13 @@ import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.power.domain.interactor.PowerInteractorFactory; import com.android.systemui.res.R; import com.android.systemui.scene.SceneTestUtils; -import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; import com.android.systemui.shade.ShadeViewStateProvider; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository; +import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -156,7 +157,6 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { public void setup() throws Exception { mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW, false); mShadeViewStateProvider = new TestShadeViewStateProvider(); - mShadeViewStateProvider = new TestShadeViewStateProvider(); MockitoAnnotations.initMocks(this); @@ -176,7 +176,9 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { mViewModel = new KeyguardStatusBarViewModel( mTestScope.getBackgroundScope(), - mKeyguardInteractor); + mKeyguardInteractor, + new KeyguardStatusBarInteractor(new FakeKeyguardStatusBarRepository()), + mBatteryController); allowTestableLooperAsMainThread(); TestableLooper.get(this).runWithLooper(() -> { @@ -320,6 +322,15 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { } @Test + public void setBatteryListening_true_flagOn_callbackNotAdded() { + mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW, true); + + mController.setBatteryListening(true); + + verify(mBatteryController, never()).addCallback(any()); + } + + @Test public void updateTopClipping_viewClippingUpdated() { int viewTop = 20; mKeyguardStatusBarView.setTop(viewTop); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt index f4078d59bbdb..1bc346de1568 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt @@ -29,13 +29,23 @@ import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.scene.SceneTestUtils import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository +import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor +import com.android.systemui.statusbar.policy.BatteryController +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test +import org.mockito.Mockito.verify @SmallTest +@OptIn(ExperimentalCoroutinesApi::class) class KeyguardStatusBarViewModelTest : SysuiTestCase() { private val testScope = TestScope() private val sceneTestUtils = SceneTestUtils(this) @@ -54,11 +64,18 @@ class KeyguardStatusBarViewModelTest : SysuiTestCase() { ) { sceneTestUtils.sceneInteractor() } + private val keyguardStatusBarInteractor = + KeyguardStatusBarInteractor( + FakeKeyguardStatusBarRepository(), + ) + private val batteryController = mock<BatteryController>() private val underTest = KeyguardStatusBarViewModel( testScope.backgroundScope, keyguardInteractor, + keyguardStatusBarInteractor, + batteryController, ) @Test @@ -102,4 +119,46 @@ class KeyguardStatusBarViewModelTest : SysuiTestCase() { assertThat(latest).isTrue() } + + @Test + fun isBatteryCharging_matchesCallback() = + testScope.runTest { + val latest by collectLastValue(underTest.isBatteryCharging) + runCurrent() + + val captor = argumentCaptor<BatteryController.BatteryStateChangeCallback>() + verify(batteryController).addCallback(capture(captor)) + val callback = captor.value + + callback.onBatteryLevelChanged( + /* level= */ 2, + /* pluggedIn= */ false, + /* charging= */ true, + ) + + assertThat(latest).isTrue() + + callback.onBatteryLevelChanged( + /* level= */ 2, + /* pluggedIn= */ true, + /* charging= */ false, + ) + + assertThat(latest).isFalse() + } + + @Test + fun isBatteryCharging_unregistersWhenNotListening() = + testScope.runTest { + val job = underTest.isBatteryCharging.launchIn(this) + runCurrent() + + val captor = argumentCaptor<BatteryController.BatteryStateChangeCallback>() + verify(batteryController).addCallback(capture(captor)) + + job.cancel() + runCurrent() + + verify(batteryController).removeCallback(captor.value) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt index 773a0d8ceb64..020903057eb3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt @@ -49,6 +49,7 @@ class TelephonyRepositoryImplTest : SysuiTestCase() { underTest = TelephonyRepositoryImpl( + applicationContext = context, manager = manager, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/FakeUserSwitcherRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/FakeUserSwitcherRepository.kt new file mode 100644 index 000000000000..758fe93a658c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/FakeUserSwitcherRepository.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.user.data.repository + +import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel +import kotlinx.coroutines.flow.MutableStateFlow + +class FakeUserSwitcherRepository : UserSwitcherRepository { + override val isEnabled = MutableStateFlow(false) + override val userSwitcherStatus = + MutableStateFlow<UserSwitcherStatusModel>(UserSwitcherStatusModel.Disabled) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 424218cfc40c..409ba4801d0d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -2262,5 +2262,8 @@ public class BubblesTest extends SysuiTestCase { public boolean isBubbleBarEnabled() { return mIsBubbleBarEnabled; } + + @Override + public void refresh() {} } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java index cd009dff27ff..d6632a3c5ea7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java @@ -38,16 +38,12 @@ import androidx.test.InstrumentationRegistry; import androidx.test.uiautomator.UiDevice; import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.broadcast.FakeBroadcastDispatcher; import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger; -import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.dump.DumpManager; -import com.android.systemui.plugins.FalsingManager; import com.android.systemui.settings.UserTracker; -import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.phone.SystemUIDialogManager; import org.junit.After; @@ -128,20 +124,8 @@ public abstract class SysuiTestCase { // reference and are never sent to the Context. This will also prevent a real // BroadcastDispatcher from actually registering receivers. mDependency.injectTestDependency(BroadcastDispatcher.class, mFakeBroadcastDispatcher); - // A lot of tests get the FalsingManager, often via several layers of indirection. - // None of them actually need it. - mDependency.injectTestDependency(FalsingManager.class, new FalsingManagerFake()); mDependency.injectMockDependency(KeyguardUpdateMonitor.class); - // A lot of tests get the LocalBluetoothManager, often via several layers of indirection. - // None of them actually need it. - mDependency.injectMockDependency(LocalBluetoothManager.class); - - // Notifications tests are injecting one of these, causing many classes (including - // KeyguardUpdateMonitor to be created (injected). - // TODO(b/1531701009) Clean up NotificationContentView creation to prevent this - mDependency.injectMockDependency(SmartReplyController.class); - // Make sure that all tests on any SystemUIDialog does not crash because this dependency // is missing (constructing the actual one would throw). // TODO(b/219008720): Remove this. diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt index 1a893f8c523c..bf77b1a050cd 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt @@ -36,8 +36,6 @@ import com.android.systemui.qs.FgsManagerController import com.android.systemui.qs.QSSecurityFooterUtils import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepository import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepositoryImpl -import com.android.systemui.qs.footer.data.repository.UserSwitcherRepository -import com.android.systemui.qs.footer.data.repository.UserSwitcherRepositoryImpl import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel @@ -51,6 +49,8 @@ import com.android.systemui.statusbar.policy.FakeUserInfoController import com.android.systemui.statusbar.policy.SecurityController import com.android.systemui.statusbar.policy.UserInfoController import com.android.systemui.statusbar.policy.UserSwitcherController +import com.android.systemui.user.data.repository.UserSwitcherRepository +import com.android.systemui.user.data.repository.UserSwitcherRepositoryImpl import com.android.systemui.user.domain.interactor.UserInteractor import com.android.systemui.util.mockito.mock import com.android.systemui.util.settings.FakeSettings diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt new file mode 100644 index 000000000000..201926df5a5c --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.viewmodel + +import androidx.annotation.StringRes +import com.android.internal.logging.InstanceId +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.qs.pipeline.shared.TileSpec + +object QSTileConfigTestBuilder { + + fun build(configure: BuildingScope.() -> Unit = {}): QSTileConfig = + BuildingScope().apply(configure).build() + + class BuildingScope { + var tileSpec: TileSpec = TileSpec.create("test_spec") + var tileIcon: Icon = Icon.Resource(0, ContentDescription.Resource(0)) + @StringRes var tileLabel: Int = 0 + var instanceId: InstanceId = InstanceId.fakeInstanceId(0) + var metricsSpec: String = tileSpec.spec + var policy: QSTilePolicy = QSTilePolicy.NoRestrictions + + fun build() = + QSTileConfig( + tileSpec, + tileIcon, + tileLabel, + instanceId, + metricsSpec, + policy, + ) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index 67777349ce12..766f748bece8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt @@ -17,6 +17,8 @@ package com.android.systemui.scene import android.content.pm.UserInfo +import android.graphics.Bitmap +import android.graphics.drawable.BitmapDrawable import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel import com.android.systemui.authentication.data.repository.AuthenticationRepository @@ -30,6 +32,7 @@ import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.classifier.domain.interactor.FalsingInteractor +import com.android.systemui.common.shared.model.Text import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.communal.data.repository.FakeCommunalRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository @@ -51,12 +54,17 @@ import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.telephony.data.repository.FakeTelephonyRepository +import com.android.systemui.telephony.domain.interactor.TelephonyInteractor import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.data.repository.UserRepository +import com.android.systemui.user.ui.viewmodel.UserActionViewModel +import com.android.systemui.user.ui.viewmodel.UserViewModel import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope @@ -209,6 +217,7 @@ class SceneTestUtils( fun bouncerViewModel( bouncerInteractor: BouncerInteractor, authenticationInteractor: AuthenticationInteractor, + users: List<UserViewModel> = createUsers(), ): BouncerViewModel { return BouncerViewModel( applicationContext = context, @@ -217,6 +226,13 @@ class SceneTestUtils( bouncerInteractor = bouncerInteractor, authenticationInteractor = authenticationInteractor, flags = sceneContainerFlags, + selectedUser = flowOf(users.first { it.isSelectionMarkerVisible }), + users = flowOf(users), + userSwitcherMenu = flowOf(createMenuActions()), + telephonyInteractor = + TelephonyInteractor( + repository = FakeTelephonyRepository(), + ), ) } @@ -232,6 +248,43 @@ class SceneTestUtils( return testScope.backgroundScope } + private fun createUsers( + count: Int = 3, + selectedIndex: Int = 0, + ): List<UserViewModel> { + check(selectedIndex in 0 until count) + + return buildList { + repeat(count) { index -> + add( + UserViewModel( + viewKey = index, + name = Text.Loaded("name_$index"), + image = BitmapDrawable(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)), + isSelectionMarkerVisible = index == selectedIndex, + alpha = 1f, + onClicked = {}, + ) + ) + } + } + } + + private fun createMenuActions(): List<UserActionViewModel> { + return buildList { + repeat(3) { index -> + add( + UserActionViewModel( + viewKey = index.toLong(), + iconResourceId = 0, + textResourceId = 0, + onClicked = {}, + ) + ) + } + } + } + companion object { fun DomainLayerAuthenticationMethodModel.toDataLayer(): DataLayerAuthenticationMethodModel { return when (this) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt index 7c70846d4d76..992ac62fe3c4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt @@ -31,9 +31,16 @@ class FakeTelephonyRepository @Inject constructor() : TelephonyRepository { private val _callState = MutableStateFlow(0) override val callState: Flow<Int> = _callState.asStateFlow() + override var hasTelephonyRadio: Boolean = true + private set + fun setCallState(value: Int) { _callState.value = value } + + fun setHasRadio(hasRadio: Boolean) { + this.hasTelephonyRadio = hasRadio + } } @Module diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index cad8fcf39b32..c7b53c55d89b 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -223,6 +223,9 @@ public final class AutofillManagerService @GuardedBy("mFlagLock") private String mPccProviderHints; + @GuardedBy("mFlagLock") + private int mMaxInputLengthForAutofill; + // Default flag values for Autofill PCC private static final String DEFAULT_PCC_FEATURE_PROVIDER_HINTS = ""; @@ -694,6 +697,10 @@ public final class AutofillManagerService DeviceConfig.NAMESPACE_AUTOFILL, AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS, DEFAULT_PCC_FEATURE_PROVIDER_HINTS); + mMaxInputLengthForAutofill = DeviceConfig.getInt( + DeviceConfig.NAMESPACE_AUTOFILL, + AutofillFeatureFlags.DEVICE_CONFIG_MAX_INPUT_LENGTH_FOR_AUTOFILL, + AutofillFeatureFlags.DEFAULT_MAX_INPUT_LENGTH_FOR_AUTOFILL); if (verbose) { Slog.v(mTag, "setDeviceConfigProperties() for PCC: " + "mPccClassificationEnabled=" + mPccClassificationEnabled @@ -988,6 +995,15 @@ public final class AutofillManagerService } } + /** + * Return the max suggestion length + */ + public int getMaxInputLengthForAutofill() { + synchronized (mFlagLock) { + return mMaxInputLengthForAutofill; + } + } + @Nullable @VisibleForTesting static Map<String, String[]> getAllowedCompatModePackages(String setting) { diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 4e5b0589446a..0220deca18c1 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -4596,7 +4596,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState getUiForShowing().showFillUi(filledId, response, filterText, mService.getServicePackageName(), mComponentName, - targetLabel, targetIcon, this, mContext, id, mCompatMode); + targetLabel, targetIcon, this, mContext, id, mCompatMode, + mService.getMaster().getMaxInputLengthForAutofill()); synchronized (mLock) { mPresentationStatsEventLogger.maybeSetCountShown( @@ -4856,7 +4857,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState public void onInflate() { Session.this.onShown(UI_TYPE_INLINE); } - }); + }, mService.getMaster().getMaxInputLengthForAutofill()); return mInlineSessionController.setInlineFillUiLocked(inlineFillUi); } diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java index d479dfb512ca..602855da7ddf 100644 --- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java @@ -204,12 +204,14 @@ public final class AutoFillUI { * @param context context with the proper state (like display id) to show the UI * @param sessionId id of the autofill session * @param compatMode whether the app is being autofilled in compatibility mode. + * @param maxInputLengthForAutofill max user input to provide suggestion */ public void showFillUi(@NonNull AutofillId focusedId, @NonNull FillResponse response, @Nullable String filterText, @Nullable String servicePackageName, @NonNull ComponentName componentName, @NonNull CharSequence serviceLabel, @NonNull Drawable serviceIcon, @NonNull AutoFillUiCallback callback, - @NonNull Context context, int sessionId, boolean compatMode) { + @NonNull Context context, int sessionId, boolean compatMode, + int maxInputLengthForAutofill) { if (sDebug) { final int size = filterText == null ? 0 : filterText.length(); Slogf.d(TAG, "showFillUi(): id=%s, filter=%d chars, displayId=%d", focusedId, size, @@ -229,7 +231,8 @@ public final class AutoFillUI { } hideAllUiThread(callback); mFillUi = new FillUi(context, response, focusedId, filterText, mOverlayControl, - serviceLabel, serviceIcon, mUiModeMgr.isNightMode(), new FillUi.Callback() { + serviceLabel, serviceIcon, mUiModeMgr.isNightMode(), maxInputLengthForAutofill, + new FillUi.Callback() { @Override public void onResponsePicked(FillResponse response) { log.setType(MetricsEvent.TYPE_DETAIL); diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java index 0a8fe62a6617..b2716ecc0cfc 100644 --- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java @@ -127,6 +127,8 @@ final class FillUi { private final int mThemeId; + private int mMaxInputLengthForAutofill; + public static boolean isFullScreen(Context context) { if (sFullScreenMode != null) { if (sVerbose) Slog.v(TAG, "forcing full-screen mode to " + sFullScreenMode); @@ -138,7 +140,8 @@ final class FillUi { FillUi(@NonNull Context context, @NonNull FillResponse response, @NonNull AutofillId focusedViewId, @Nullable String filterText, @NonNull OverlayControl overlayControl, @NonNull CharSequence serviceLabel, - @NonNull Drawable serviceIcon, boolean nightMode, @NonNull Callback callback) { + @NonNull Drawable serviceIcon, boolean nightMode, int maxInputLengthForAutofill, + @NonNull Callback callback) { if (sVerbose) { Slogf.v(TAG, "nightMode: %b displayId: %d", nightMode, context.getDisplayId()); } @@ -146,6 +149,7 @@ final class FillUi { mCallback = callback; mFullScreen = isFullScreen(context); mContext = new ContextThemeWrapper(context, mThemeId); + mMaxInputLengthForAutofill = maxInputLengthForAutofill; final LayoutInflater inflater = LayoutInflater.from(mContext); @@ -432,10 +436,11 @@ final class FillUi { Slog.d(TAG, "No dataset matches filter with " + size + " chars"); } mCallback.requestHideFillUi(); - } else if (size > 3) { - // Do not show suggestion if user entered four or more characters + } else if (size > mMaxInputLengthForAutofill) { + // Do not show suggestion if user entered more than the maximum suggesiton length if (sDebug) { - Slog.d(TAG, "Not showing fill UI because user entered more than 3 characters"); + Slog.d(TAG, "Not showing fill UI because user entered more than " + + mMaxInputLengthForAutofill + " characters"); } mCallback.requestHideFillUi(); } else { diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java b/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java index 24eab0b78c86..c734680009c6 100644 --- a/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java @@ -98,6 +98,11 @@ public final class InlineFillUi { } /** + * If user enters more characters than this length, the autofill suggestion won't be shown. + */ + private int mMaxInputLengthForAutofill = Integer.MAX_VALUE; + + /** * Encapsulates various arguments used by {@link #forAutofill} and {@link #forAugmentedAutofill} */ public static class InlineFillUiInfo { @@ -128,20 +133,22 @@ public final class InlineFillUi { @NonNull public static InlineFillUi forAutofill(@NonNull InlineFillUiInfo inlineFillUiInfo, @NonNull FillResponse response, - @NonNull InlineSuggestionUiCallback uiCallback) { + @NonNull InlineSuggestionUiCallback uiCallback, int maxInputLengthForAutofill) { if (response.getAuthentication() != null && response.getInlinePresentation() != null) { InlineSuggestion inlineAuthentication = InlineSuggestionFactory.createInlineAuthentication(inlineFillUiInfo, response, uiCallback); - return new InlineFillUi(inlineFillUiInfo, inlineAuthentication); + return new InlineFillUi(inlineFillUiInfo, inlineAuthentication, + maxInputLengthForAutofill); } else if (response.getDatasets() != null) { SparseArray<Pair<Dataset, InlineSuggestion>> inlineSuggestions = InlineSuggestionFactory.createInlineSuggestions(inlineFillUiInfo, InlineSuggestionInfo.SOURCE_AUTOFILL, response.getDatasets(), uiCallback); - return new InlineFillUi(inlineFillUiInfo, inlineSuggestions); + return new InlineFillUi(inlineFillUiInfo, inlineSuggestions, + maxInputLengthForAutofill); } - return new InlineFillUi(inlineFillUiInfo, new SparseArray<>()); + return new InlineFillUi(inlineFillUiInfo, new SparseArray<>(), maxInputLengthForAutofill); } /** @@ -157,6 +164,9 @@ public final class InlineFillUi { return new InlineFillUi(inlineFillUiInfo, inlineSuggestions); } + /** + * Used by augmented autofill + */ private InlineFillUi(@Nullable InlineFillUiInfo inlineFillUiInfo, @NonNull SparseArray<Pair<Dataset, InlineSuggestion>> inlineSuggestions) { mAutofillId = inlineFillUiInfo.mFocusedId; @@ -171,13 +181,36 @@ public final class InlineFillUi { mFilterText = inlineFillUiInfo.mFilterText; } + /** + * Used by normal autofill + */ + private InlineFillUi(@Nullable InlineFillUiInfo inlineFillUiInfo, + @NonNull SparseArray<Pair<Dataset, InlineSuggestion>> inlineSuggestions, + int maxInputLengthForAutofill) { + mAutofillId = inlineFillUiInfo.mFocusedId; + int size = inlineSuggestions.size(); + mDatasets = new ArrayList<>(size); + mInlineSuggestions = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + Pair<Dataset, InlineSuggestion> value = inlineSuggestions.valueAt(i); + mDatasets.add(value.first); + mInlineSuggestions.add(value.second); + } + mFilterText = inlineFillUiInfo.mFilterText; + mMaxInputLengthForAutofill = maxInputLengthForAutofill; + } + + /** + * Used by normal autofill + */ private InlineFillUi(@NonNull InlineFillUiInfo inlineFillUiInfo, - @NonNull InlineSuggestion inlineSuggestion) { + @NonNull InlineSuggestion inlineSuggestion, int maxInputLengthForAutofill) { mAutofillId = inlineFillUiInfo.mFocusedId; mDatasets = null; mInlineSuggestions = new ArrayList<>(); mInlineSuggestions.add(inlineSuggestion); mFilterText = inlineFillUiInfo.mFilterText; + mMaxInputLengthForAutofill = maxInputLengthForAutofill; } /** @@ -217,11 +250,11 @@ public final class InlineFillUi { return new InlineSuggestionsResponse(inlineSuggestions); } - // Do not show suggestion if user entered four or more characters - if (!TextUtils.isEmpty(mFilterText) && mFilterText.length() > 3) { + // Do not show inline suggestion if user entered more than a certain number of characters. + if (!TextUtils.isEmpty(mFilterText) && mFilterText.length() > mMaxInputLengthForAutofill) { if (sVerbose) { - Slog.v(TAG, "Not showing inline suggestion because user entered more than 3 " - + "characters"); + Slog.v(TAG, "Not showing inline suggestion when user entered more than " + + mMaxInputLengthForAutofill + " characters"); } return new InlineSuggestionsResponse(inlineSuggestions); } diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java index 9dd0dca47f0e..852e36dcc605 100644 --- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java +++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java @@ -218,6 +218,9 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController void setActivityLaunchDefaultAllowed(boolean activityLaunchDefaultAllowed) { synchronized (mGenericWindowPolicyControllerLock) { + if (mActivityLaunchAllowedByDefault != activityLaunchDefaultAllowed) { + mActivityPolicyExemptions.clear(); + } mActivityLaunchAllowedByDefault = activityLaunchDefaultAllowed; } } diff --git a/services/core/java/com/android/server/ExplicitHealthCheckController.java b/services/core/java/com/android/server/ExplicitHealthCheckController.java index 20de40e73130..3d610d3747c9 100644 --- a/services/core/java/com/android/server/ExplicitHealthCheckController.java +++ b/services/core/java/com/android/server/ExplicitHealthCheckController.java @@ -343,7 +343,7 @@ class ExplicitHealthCheckController { }; mContext.bindServiceAsUser(intent, mConnection, - Context.BIND_AUTO_CREATE, UserHandle.of(UserHandle.USER_SYSTEM)); + Context.BIND_AUTO_CREATE, UserHandle.SYSTEM); Slog.i(TAG, "Explicit health check service is bound"); } } diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java index c094c12cc6d8..dd543349fc4d 100644 --- a/services/core/java/com/android/server/RescueParty.java +++ b/services/core/java/com/android/server/RescueParty.java @@ -522,7 +522,8 @@ public class RescueParty { Exception res = null; final ContentResolver resolver = context.getContentResolver(); try { - Settings.Global.resetToDefaultsAsUser(resolver, null, mode, UserHandle.USER_SYSTEM); + Settings.Global.resetToDefaultsAsUser(resolver, null, mode, + UserHandle.SYSTEM.getIdentifier()); } catch (Exception e) { res = new RuntimeException("Failed to reset global settings", e); } @@ -779,12 +780,13 @@ public class RescueParty { } private static int[] getAllUserIds() { - int[] userIds = { UserHandle.USER_SYSTEM }; + int systemUserId = UserHandle.SYSTEM.getIdentifier(); + int[] userIds = { systemUserId }; try { for (File file : FileUtils.listFilesOrEmpty(Environment.getDataSystemDeDirectory())) { try { final int userId = Integer.parseInt(file.getName()); - if (userId != UserHandle.USER_SYSTEM) { + if (userId != systemUserId) { userIds = ArrayUtils.appendInt(userIds, userId); } } catch (NumberFormatException ignored) { diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index 6e984bb4bb7b..b05b397a45de 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -330,7 +330,7 @@ public class Watchdog implements Dumpable { String describeBlockedStateLocked() { final String prefix; if (mCurrentMonitor == null) { - prefix = "Blocked in handler on "; + prefix = "Blocked in handler"; } else { prefix = "Blocked in monitor " + mCurrentMonitor.getClass().getName(); } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 0615ecf029fa..a97675f1d776 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -281,8 +281,8 @@ public final class ProcessList { // don't have an oom adj assigned by the system). public static final int NATIVE_ADJ = -1000; - // Memory pages are 4K. - static final int PAGE_SIZE = 4 * 1024; + // Memory page size. + static final int PAGE_SIZE = (int) Os.sysconf(OsConstants._SC_PAGESIZE); // Activity manager's version of an undefined schedule group static final int SCHED_GROUP_UNDEFINED = Integer.MIN_VALUE; diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java index a680f5001479..cd3d2f031455 100644 --- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java +++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java @@ -16,6 +16,8 @@ package com.android.server.graphics.fonts; +import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE; + import static com.android.server.graphics.fonts.FontManagerService.SystemFontException; import android.annotation.NonNull; @@ -581,7 +583,8 @@ final class UpdatableFontDir { font.getFontStyle(), font.getIndex(), font.getFontVariationSettings(), null)); } FontConfig.FontFamily family = new FontConfig.FontFamily(resolvedFonts, - LocaleList.getEmptyLocaleList(), FontConfig.FontFamily.VARIANT_DEFAULT); + LocaleList.getEmptyLocaleList(), FontConfig.FontFamily.VARIANT_DEFAULT, + VARIABLE_FONT_FAMILY_TYPE_NONE); return new FontConfig.NamedFamilyList(Collections.singletonList(family), fontFamily.getName()); } diff --git a/services/core/java/com/android/server/input/InputShellCommand.java b/services/core/java/com/android/server/input/InputShellCommand.java index 16b23ca28e36..138186ba6191 100644 --- a/services/core/java/com/android/server/input/InputShellCommand.java +++ b/services/core/java/com/android/server/input/InputShellCommand.java @@ -18,6 +18,7 @@ package com.android.server.input; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; +import static android.view.InputDevice.SOURCE_CLASS_POINTER; import static android.view.KeyEvent.KEYCODE_ALT_LEFT; import static android.view.KeyEvent.KEYCODE_ALT_RIGHT; import static android.view.KeyEvent.KEYCODE_CTRL_LEFT; @@ -38,6 +39,11 @@ import static android.view.KeyEvent.META_META_RIGHT_ON; import static android.view.KeyEvent.META_SHIFT_LEFT_ON; import static android.view.KeyEvent.META_SHIFT_ON; import static android.view.KeyEvent.META_SHIFT_RIGHT_ON; +import static android.view.MotionEvent.AXIS_HSCROLL; +import static android.view.MotionEvent.AXIS_SCROLL; +import static android.view.MotionEvent.AXIS_VSCROLL; +import static android.view.MotionEvent.AXIS_X; +import static android.view.MotionEvent.AXIS_Y; import static java.util.Collections.unmodifiableMap; @@ -47,14 +53,21 @@ import android.os.ShellCommand; import android.os.SystemClock; import android.util.ArrayMap; import android.util.IntArray; +import android.util.Pair; import android.view.InputDevice; +import android.view.InputEvent; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.ViewConfiguration; +import com.android.internal.annotations.VisibleForTesting; + import java.io.PrintWriter; +import java.util.HashMap; import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; /** * Command that sends input events to the device. @@ -107,15 +120,31 @@ public class InputShellCommand extends ShellCommand { map.put("touchpad", InputDevice.SOURCE_TOUCHPAD); map.put("touchnavigation", InputDevice.SOURCE_TOUCH_NAVIGATION); map.put("joystick", InputDevice.SOURCE_JOYSTICK); + map.put("rotaryencoder", InputDevice.SOURCE_ROTARY_ENCODER); SOURCES = unmodifiableMap(map); } + public InputShellCommand() { + this(InputShellCommand::injectInputEvent); + } + + @VisibleForTesting + InputShellCommand(BiConsumer<InputEvent, Integer> inputEventInjector) { + mInputEventInjector = inputEventInjector;; + } + + private static void injectInputEvent(InputEvent event, Integer injectMode) { + InputManagerGlobal.getInstance().injectInputEvent(event, injectMode); + } + + private final BiConsumer<InputEvent, Integer> mInputEventInjector; + private void injectKeyEvent(KeyEvent event, boolean async) { int injectMode = async ? InputManager.INJECT_INPUT_EVENT_MODE_ASYNC : InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH; - InputManagerGlobal.getInstance().injectInputEvent(event, injectMode); + mInputEventInjector.accept(event, injectMode); } private int getInputDeviceId(int inputSource) { @@ -161,19 +190,41 @@ public class InputShellCommand extends ShellCommand { */ private void injectMotionEvent(int inputSource, int action, long downTime, long when, float x, float y, float pressure, int displayId) { + final Map<Integer, Float> axisValues = + Map.of( + MotionEvent.AXIS_X, x, + MotionEvent.AXIS_Y, y, + MotionEvent.AXIS_PRESSURE, pressure); + injectMotionEvent(inputSource, action, downTime, when, axisValues, displayId); + } + + /** + * Builds a MotionEvent and injects it into the event stream. + * + * @param inputSource the InputDevice.SOURCE_* sending the input event + * @param action the MotionEvent.ACTION_* for the event + * @param downTime the value of the ACTION_DOWN event happened + * @param when the value of SystemClock.uptimeMillis() at which the event happened + * @param axisValues a map of an axis to the respective axis value + * @param displayId the ID of the display associated to the event + */ + private void injectMotionEvent(int inputSource, int action, long downTime, long when, + Map<Integer, Float> axisValues, int displayId) { final int pointerCount = 1; MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[pointerCount]; - MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[pointerCount]; for (int i = 0; i < pointerCount; i++) { pointerProperties[i] = new MotionEvent.PointerProperties(); pointerProperties[i].id = i; pointerProperties[i].toolType = getToolType(inputSource); + } + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[pointerCount]; + for (int i = 0; i < pointerCount; i++) { pointerCoords[i] = new MotionEvent.PointerCoords(); - pointerCoords[i].x = x; - pointerCoords[i].y = y; - pointerCoords[i].pressure = pressure; pointerCoords[i].size = DEFAULT_SIZE; + for (var entry : axisValues.entrySet()) { + pointerCoords[i].setAxisValue(entry.getKey(), entry.getValue()); + } } if (displayId == INVALID_DISPLAY && (inputSource & InputDevice.SOURCE_CLASS_POINTER) != 0) { @@ -183,7 +234,7 @@ public class InputShellCommand extends ShellCommand { pointerProperties, pointerCoords, DEFAULT_META_STATE, DEFAULT_BUTTON_STATE, DEFAULT_PRECISION_X, DEFAULT_PRECISION_Y, getInputDeviceId(inputSource), DEFAULT_EDGE_FLAGS, inputSource, displayId, DEFAULT_FLAGS); - InputManagerGlobal.getInstance().injectInputEvent(event, + mInputEventInjector.accept(event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH); } @@ -246,7 +297,9 @@ public class InputShellCommand extends ShellCommand { runPress(inputSource, displayId); } else if ("roll".equals(arg)) { runRoll(inputSource, displayId); - } else if ("motionevent".equals(arg)) { + } else if ("scroll".equals(arg)) { + runScroll(inputSource, displayId); + } else if ("motionevent".equals(arg)) { runMotionEvent(inputSource, displayId); } else if ("keycombination".equals(arg)) { runKeyCombination(inputSource, displayId); @@ -268,13 +321,18 @@ public class InputShellCommand extends ShellCommand { for (String src : SOURCES.keySet()) { out.println(" " + src); } + out.println("[axis_value] represents an option specifying the value of a given axis "); + out.println(" The syntax is as follows: --axis <axis_name>,<axis_value>"); + out.println(" where <axis_name> is the name of the axis as defined in "); + out.println(" MotionEvent without the AXIS_ prefix (e.g. SCROLL, X)"); + out.println(" Sample [axis_values] entry: `--axis Y,3`, `--axis SCROLL,-2`"); out.println(); out.printf("-d: specify the display ID.\n (Default: %d for key event, " + "%d for motion event if not specified.)", INVALID_DISPLAY, DEFAULT_DISPLAY); out.println(); out.println("The commands and default sources are:"); - out.println(" text <string> (Default: touchscreen)"); + out.println(" text <string> (Default: keyboard)"); out.println(" keyevent [--longpress|--doubletap|--async" + "|--delay <duration between keycodes in ms>]" + " <key code number or name> ..." @@ -287,6 +345,13 @@ public class InputShellCommand extends ShellCommand { out.println(" press (Default: trackball)"); out.println(" roll <dx> <dy> (Default: trackball)"); out.println(" motionevent <DOWN|UP|MOVE|CANCEL> <x> <y> (Default: touchscreen)"); + out.println(" scroll (Default: rotaryencoder). Has the following syntax:"); + out.println(" scroll <x> <y> [axis_value] (for pointer-based sources)"); + out.println(" scroll [axis_value] (for non-pointer-based sources)"); + out.println(" Axis options: SCROLL, HSCROLL, VSCROLL"); + out.println(" None or one or multiple axis value options can be specified."); + out.println(" To specify multiple axes, use one axis option for per axis."); + out.println(" Example: `scroll --axis VSCROLL,2 --axis SCROLL,-2.4`"); out.println(" keycombination [-t duration(ms)] <key code 1> <key code 2> ..." + " (Default: keyboard, the key order is important here.)"); } @@ -452,6 +517,62 @@ public class InputShellCommand extends ShellCommand { Float.parseFloat(getNextArgRequired()), displayId); } + private void runScroll(int inputSource, int displayId) { + inputSource = getSource(inputSource, InputDevice.SOURCE_ROTARY_ENCODER); + final boolean isPointerEvent = (inputSource & SOURCE_CLASS_POINTER) == SOURCE_CLASS_POINTER; + final Map<Integer, Float> axisValues = new HashMap<>(); + if (isPointerEvent) { + axisValues.put(AXIS_X, Float.parseFloat(getNextArgRequired())); + axisValues.put(AXIS_Y, Float.parseFloat(getNextArgRequired())); + } + final Set<Integer> supportedAxes = Set.of(AXIS_HSCROLL, AXIS_VSCROLL, AXIS_SCROLL); + String nextOption; + while ((nextOption = getNextOption()) != null) { + switch (nextOption) { + case "--axis": + final Pair<Integer, Float> axisAndValue = readAxisOptionValues(supportedAxes); + axisValues.put(axisAndValue.first, axisAndValue.second); + break; + default: + throw new IllegalArgumentException("Unsupported option: " + nextOption); + } + } + final long now = SystemClock.uptimeMillis(); + injectMotionEvent(inputSource, MotionEvent.ACTION_SCROLL, now /* downTime */, + now /* when */, axisValues, displayId); + } + + /** + * Reads an axis value for the `--axis` command option. + * + * <p>The value for an `--axis` should be a single string containing the axis name without the + * `AXIS_` prefix, and comma, and a float value representing the value for the respective axis. + * + * <p>Example: `--axis SCROLL,2.4` represents "a value of 2.4 for AXIS_SCROLL" + * + * <p>This method should be called after the `--axis` option has already been read. + * + * @param supportedAxes the set of allowed axes to be read. If an axis option is read where the + * axis is not present in this set, this method throws an {@link IllegalArgumentException}. + * @return a Pair of the axis and its respective value. + */ + private Pair<Integer, Float> readAxisOptionValues(Set<Integer> supportedAxes) { + final String optionValue = getNextArgRequired(); + final String[] axisAndValue = optionValue.split(","); + if (axisAndValue.length != 2) { + throw new IllegalArgumentException("Invalid --axis option value: " + optionValue); + } + final String axisName = "AXIS_" + axisAndValue[0]; + final int axis = MotionEvent.axisFromString(axisName); + if (axis == -1) { + throw new IllegalArgumentException("Invalid axis name: " + axisName); + } + if (!supportedAxes.contains(axis)) { + throw new IllegalArgumentException("Unsupported axis: " + axisName); + } + return Pair.create(axis, Float.parseFloat(axisAndValue[1])); + } + /** * Sends a simple zero-pressure move event. * diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java index c83a96938325..431aabdc3018 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java @@ -69,27 +69,16 @@ final class InputMethodSubtypeSwitchingController { mIsSystemLanguage = true; } else { // TODO: Use Locale#getLanguage or Locale#toLanguageTag - final String systemLanguage = parseLanguageFromLocaleString(systemLocale); - final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale); + final String systemLanguage = LocaleUtils.getLanguageFromLocaleString( + systemLocale); + final String subtypeLanguage = LocaleUtils.getLanguageFromLocaleString( + subtypeLocale); mIsSystemLanguage = systemLanguage.length() >= 2 && systemLanguage.equals(subtypeLanguage); } } } - /** - * Returns the language component of a given locale string. - * TODO: Use {@link Locale#getLanguage()} instead. - */ - private static String parseLanguageFromLocaleString(final String locale) { - final int idx = locale.indexOf('_'); - if (idx < 0) { - return locale; - } else { - return locale.substring(0, idx); - } - } - private static int compareNullableCharSequences(@Nullable CharSequence c1, @Nullable CharSequence c2) { // For historical reasons, an empty text needs to put at the last. @@ -116,7 +105,7 @@ final class InputMethodSubtypeSwitchingController { * * @param other the object to be compared. * @return a negative integer, zero, or positive integer as this object is less than, equal - * to, or greater than the specified <code>other</code> object. + * to, or greater than the specified <code>other</code> object. */ @Override public int compareTo(ImeSubtypeListItem other) { @@ -253,9 +242,10 @@ final class InputMethodSubtypeSwitchingController { /** * Returns the index of the specified input method and subtype in the given list. - * @param imi The {@link InputMethodInfo} to be searched. + * + * @param imi The {@link InputMethodInfo} to be searched. * @param subtype The {@link InputMethodSubtype} to be searched. null if the input method - * does not have a subtype. + * does not have a subtype. * @return The index in the given list. -1 if not found. */ private int getIndex(InputMethodInfo imi, InputMethodSubtype subtype) { @@ -327,6 +317,7 @@ final class InputMethodSubtypeSwitchingController { * {@link #mUsageHistoryOfSubtypeListItemIndex}. * <p>We call the index of {@link #mUsageHistoryOfSubtypeListItemIndex} as "Usage Rank" * so as not to be confused with the index in {@link #mImeSubtypeList}. + * * @return -1 when the specified item doesn't belong to {@link #mImeSubtypeList} actually. */ private int getUsageRank(final InputMethodInfo imi, InputMethodSubtype subtype) { diff --git a/services/core/java/com/android/server/inputmethod/LocaleUtils.java b/services/core/java/com/android/server/inputmethod/LocaleUtils.java index f865e6010580..7d090dbcc5d8 100644 --- a/services/core/java/com/android/server/inputmethod/LocaleUtils.java +++ b/services/core/java/com/android/server/inputmethod/LocaleUtils.java @@ -215,12 +215,7 @@ final class LocaleUtils { * TODO: Use {@link Locale#toLanguageTag()} and {@link Locale#forLanguageTag(String)} */ static String getLanguageFromLocaleString(String locale) { - final int idx = locale.indexOf('_'); - if (idx < 0) { - return locale; - } else { - return locale.substring(0, idx); - } + return Locale.forLanguageTag(locale).getLanguage(); } static Locale getSystemLocaleFromContext(Context context) { diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 83a3125884ac..c9528d8257c4 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -707,7 +707,8 @@ class MediaRouter2ServiceImpl { } private boolean checkCallerHasSystemRoutingPermissions(int pid, int uid) { - return checkCallerHasModifyAudioRoutingPermission(pid, uid); + return checkCallerHasModifyAudioRoutingPermission(pid, uid) + || checkCallerHasBluetoothPermissions(pid, uid); } private boolean checkCallerHasModifyAudioRoutingPermission(int pid, int uid) { diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java index b8c2b8616ea6..1f12c88ede36 100644 --- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java +++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java @@ -50,7 +50,7 @@ class PackageMonitorCallbackHelper { @NonNull private final Object mLock = new Object(); - final IActivityManager mActivityManager = ActivityManager.getService(); + IActivityManager mActivityManager; @NonNull @GuardedBy("mLock") @@ -149,6 +149,9 @@ class PackageMonitorCallbackHelper { try { final int[] resolvedUserIds; if (userIds == null) { + if (mActivityManager == null) { + mActivityManager = ActivityManager.getService(); + } if (mActivityManager == null) return; resolvedUserIds = mActivityManager.getRunningUserIds(); } else { diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 237bc9203415..a450b4d71a7e 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -187,7 +187,6 @@ import android.database.ContentObserver; import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.Rect; -import android.hardware.power.Mode; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -711,11 +710,15 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { static final int POWER_MODE_REASON_CHANGE_DISPLAY = 1 << 1; /** @see UnknownAppVisibilityController */ static final int POWER_MODE_REASON_UNKNOWN_VISIBILITY = 1 << 2; - /** This can only be used by {@link #endLaunchPowerMode(int)}.*/ + /** + * This can only be used by {@link #endPowerMode(int)}. Excluding UNKNOWN_VISIBILITY because + * that is guarded by a timeout while keyguard is locked. + */ static final int POWER_MODE_REASON_ALL = (1 << 2) - 1; - /** The reasons to use {@link Mode#LAUNCH} power mode. */ - private @PowerModeReason int mLaunchPowerModeReasons; + /** The reasons to apply power modes. */ + @PowerModeReason + private int mPowerModeReasons; @Retention(RetentionPolicy.SOURCE) @IntDef({ @@ -4657,11 +4660,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } - void startLaunchPowerMode(@PowerModeReason int reason) { - if (mPowerManagerInternal != null) { - mPowerManagerInternal.setPowerMode(Mode.LAUNCH, true); - } - mLaunchPowerModeReasons |= reason; + void startPowerMode(@PowerModeReason int reason) { + final int prevReasons = mPowerModeReasons; + mPowerModeReasons |= reason; if ((reason & POWER_MODE_REASON_UNKNOWN_VISIBILITY) != 0) { if (mRetainPowerModeAndTopProcessState) { mH.removeMessages(H.END_POWER_MODE_UNKNOWN_VISIBILITY_MSG); @@ -4671,27 +4672,56 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { POWER_MODE_UNKNOWN_VISIBILITY_TIMEOUT_MS); Slog.d(TAG, "Temporarily retain top process state for launching app"); } + if (mPowerManagerInternal == null) { + return; + } + + // START_ACTIVITY can be used with UNKNOWN_VISIBILITY. CHANGE_DISPLAY should be used alone. + if ((reason & POWER_MODE_REASON_START_ACTIVITY) != 0 + && (prevReasons & POWER_MODE_REASON_START_ACTIVITY) == 0) { + Trace.instant(Trace.TRACE_TAG_WINDOW_MANAGER, "StartModeLaunch"); + mPowerManagerInternal.setPowerMode(PowerManagerInternal.MODE_LAUNCH, true); + } else if (reason == POWER_MODE_REASON_CHANGE_DISPLAY + && (prevReasons & POWER_MODE_REASON_CHANGE_DISPLAY) == 0) { + Trace.instant(Trace.TRACE_TAG_WINDOW_MANAGER, "StartModeDisplayChange"); + mPowerManagerInternal.setPowerMode(PowerManagerInternal.MODE_DISPLAY_CHANGE, true); + } } - void endLaunchPowerMode(@PowerModeReason int reason) { - if (mLaunchPowerModeReasons == 0) return; - mLaunchPowerModeReasons &= ~reason; + void endPowerMode(@PowerModeReason int reason) { + if (mPowerModeReasons == 0) return; + final int prevReasons = mPowerModeReasons; + mPowerModeReasons &= ~reason; - if ((mLaunchPowerModeReasons & POWER_MODE_REASON_UNKNOWN_VISIBILITY) != 0) { + if ((mPowerModeReasons & POWER_MODE_REASON_UNKNOWN_VISIBILITY) != 0) { boolean allResolved = true; for (int i = mRootWindowContainer.getChildCount() - 1; i >= 0; i--) { allResolved &= mRootWindowContainer.getChildAt(i).mUnknownAppVisibilityController .allResolved(); } if (allResolved) { - mLaunchPowerModeReasons &= ~POWER_MODE_REASON_UNKNOWN_VISIBILITY; + mPowerModeReasons &= ~POWER_MODE_REASON_UNKNOWN_VISIBILITY; mRetainPowerModeAndTopProcessState = false; mH.removeMessages(H.END_POWER_MODE_UNKNOWN_VISIBILITY_MSG); } } + if (mPowerManagerInternal == null) { + return; + } - if (mLaunchPowerModeReasons == 0 && mPowerManagerInternal != null) { - mPowerManagerInternal.setPowerMode(Mode.LAUNCH, false); + // If the launching apps have unknown visibility, only end launch power mode until the + // states are resolved. + final int endLaunchModeReasons = POWER_MODE_REASON_START_ACTIVITY + | POWER_MODE_REASON_UNKNOWN_VISIBILITY; + if ((prevReasons & endLaunchModeReasons) != 0 + && (mPowerModeReasons & endLaunchModeReasons) == 0) { + Trace.instant(Trace.TRACE_TAG_WINDOW_MANAGER, "EndModeLaunch"); + mPowerManagerInternal.setPowerMode(PowerManagerInternal.MODE_LAUNCH, false); + } + if ((prevReasons & POWER_MODE_REASON_CHANGE_DISPLAY) != 0 + && (mPowerModeReasons & POWER_MODE_REASON_CHANGE_DISPLAY) == 0) { + Trace.instant(Trace.TRACE_TAG_WINDOW_MANAGER, "EndModeDisplayChange"); + mPowerManagerInternal.setPowerMode(PowerManagerInternal.MODE_DISPLAY_CHANGE, false); } } @@ -5751,7 +5781,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { case END_POWER_MODE_UNKNOWN_VISIBILITY_MSG: { synchronized (mGlobalLock) { mRetainPowerModeAndTopProcessState = false; - endLaunchPowerMode(POWER_MODE_REASON_UNKNOWN_VISIBILITY); + endPowerMode(POWER_MODE_REASON_UNKNOWN_VISIBILITY); if (mTopApp != null && mTopProcessState == ActivityManager.PROCESS_STATE_TOP_SLEEPING) { // Restore the scheduling group for sleeping. diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 901975ba3614..9fd472057e43 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -2009,7 +2009,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } // End power mode launch before going sleep - mService.endLaunchPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_ALL); + mService.endPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_ALL); // Rank task layers to make sure the {@link Task#mLayerRank} is updated. mRootWindowContainer.rankTaskLayers(); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index ca42400dad26..f6fe9b1dc63d 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -3484,7 +3484,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final Transition t = controller.requestTransitionIfNeeded(TRANSIT_CHANGE, 0 /* flags */, this, this, null /* remoteTransition */, displayChange); if (t != null) { - mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY); + mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY); if (mAsyncRotationController != null) { // Give a chance to update the transform if the current rotation is changed when // some windows haven't finished previous rotation. diff --git a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java index f0757db76bda..5f4a1c5ca32c 100644 --- a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java +++ b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java @@ -123,7 +123,7 @@ public class PhysicalDisplaySwitchTransitionLauncher { displayChange); if (t != null) { - mDisplayContent.mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY); + mDisplayContent.mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY); mTransition = t; } diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index ee05e355e8ef..b738c1c4bd40 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -299,8 +299,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan // Just to be sure end the launch hint in case the target activity was never launched. // However, if we're keeping the activity and making it visible, we can leave it on. if (reorderMode != REORDER_KEEP_IN_PLACE) { - mService.endLaunchPowerMode( - ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY); + mService.endPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY); } // Once the target is shown, prevent spurious background app switches diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index ea5c9c2715b1..553375985ed6 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -3272,7 +3272,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } } // End power mode launch when idle. - mService.endLaunchPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY); + mService.endPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY); return true; } @@ -3478,7 +3478,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> reason |= ActivityTaskManagerService.POWER_MODE_REASON_UNKNOWN_VISIBILITY; } } - mService.startLaunchPowerMode(reason); + mService.startPowerMode(reason); } /** diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index c92a781746ca..710913712aa0 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -879,7 +879,7 @@ class TransitionController { // It is usually a no-op but make sure that the metric consumer is removed. mTransitionMetricsReporter.reportAnimationStart(record.getToken(), 0 /* startTime */); // It is a no-op if the transition did not change the display. - mAtm.endLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY); + mAtm.endPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY); if (!mPlayingTransitions.contains(record)) { Slog.e(TAG, "Trying to finish a non-playing transition " + record); return; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 3db7765963f7..82452ccc3d06 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -6203,7 +6203,7 @@ public class WindowManagerService extends IWindowManager.Stub mScreenFrozenLock.acquire(); // Apply launch power mode to reduce screen frozen time because orientation change may // relaunch activity and redraw windows. This may also help speed up user switching. - mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY); + mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY); mDisplayFrozen = true; mDisplayFreezeTime = SystemClock.elapsedRealtime(); @@ -6345,7 +6345,7 @@ public class WindowManagerService extends IWindowManager.Stub if (configChanged) { displayContent.sendNewConfiguration(); } - mAtmService.endLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY); + mAtmService.endPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY); mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN); } diff --git a/services/tests/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp index c7a71eeb969c..ce28682c0a25 100644 --- a/services/tests/PackageManagerServiceTests/host/Android.bp +++ b/services/tests/PackageManagerServiceTests/host/Android.bp @@ -34,8 +34,11 @@ java_test_host { ], static_libs: [ "ApexInstallHelper", + "android.security.flags-aconfig-java-host", "cts-host-utils", + "flag-junit-host", "frameworks-base-hostutils", + "kotlin-test", "PackageManagerServiceHostTestsIntentVerifyUtils", "block_device_writer_jar", ], @@ -59,6 +62,7 @@ java_test_host { ":PackageManagerTestAppUsesStaticLibrary", ":PackageManagerTestAppVersion1", ":PackageManagerTestAppVersion2", + ":PackageManagerTestAppVersion2AltKey", ":PackageManagerTestAppVersion3", ":PackageManagerTestAppVersion3Invalid", ":PackageManagerTestAppVersion4", diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt new file mode 100644 index 000000000000..c4906041ea5d --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.test + +import android.platform.test.annotations.RequiresFlagsEnabled +import android.platform.test.flag.junit.host.HostFlagsValueProvider +import com.android.internal.util.test.SystemPreparer +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test +import com.google.common.truth.Truth.assertThat +import java.io.File +import java.io.RandomAccessFile +import kotlin.test.assertNotNull +import org.junit.After +import org.junit.Before +import org.junit.ClassRule +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.rules.TemporaryFolder +import org.junit.runner.RunWith + +@RunWith(DeviceJUnit4ClassRunner::class) +@RequiresFlagsEnabled(android.security.Flags.FLAG_EXTEND_VB_CHAIN_TO_UPDATED_APK) +class TamperedUpdatedSystemPackageTest : BaseHostJUnit4Test() { + + companion object { + private const val TEST_PKG_NAME = "com.android.server.pm.test.test_app" + private const val VERSION_ONE = "PackageManagerTestAppVersion1.apk" + private const val VERSION_TWO_ALT_KEY = "PackageManagerTestAppVersion2AltKey.apk" + private const val VERSION_TWO_ALT_KEY_IDSIG = + "PackageManagerTestAppVersion2AltKey.apk.idsig" + private const val STRICT_SIGNATURE_CONFIG_PATH = + "/system/etc/sysconfig/preinstalled-packages-strict-signature.xml" + private const val TIMESTAMP_REFERENCE_FILE_PATH = "/data/local/tmp/timestamp.ref" + + @get:ClassRule + val deviceRebootRule = SystemPreparer.TestRuleDelegate(true) + } + + private val tempFolder = TemporaryFolder() + private val preparer: SystemPreparer = SystemPreparer( + tempFolder, + SystemPreparer.RebootStrategy.FULL, + deviceRebootRule + ) { this.device } + private val productPath = + HostUtils.makePathForApk("PackageManagerTestApp.apk", Partition.PRODUCT) + private lateinit var originalConfigFile: File + + @Rule + @JvmField + val checkFlagsRule = HostFlagsValueProvider.createCheckFlagsRule({ getDevice() }) + + @Rule + @JvmField + val rules = RuleChain.outerRule(tempFolder).around(preparer)!! + + @Before + @After + fun removeApk() { + device.uninstallPackage(TEST_PKG_NAME) + } + + @Before + fun backupAndModifySystemFiles() { + // Backup + device.pullFile(STRICT_SIGNATURE_CONFIG_PATH).also { + assertNotNull(it) + originalConfigFile = it + } + + // Modify to allowlist the target package on device for testing the feature + val xml = tempFolder.newFile().apply { + val newConfigText = originalConfigFile + .readText() + .replace( + "</config>", + "<require-strict-signature package=\"${TEST_PKG_NAME}\"/></config>" + ) + writeText(newConfigText) + } + device.remountSystemWritable() + device.pushFile(xml, STRICT_SIGNATURE_CONFIG_PATH) + } + + @After + fun restoreSystemFiles() { + device.remountSystemWritable() + device.pushFile(originalConfigFile, STRICT_SIGNATURE_CONFIG_PATH) + // Files pushed via a SystemPreparer are deleted automatically. + } + + @Test + fun detectApkAndXmlTamperingAtBoot() { + // Set up the scenario where both APK and packages.xml are tampered by the attacker. + // This is done by booting with the "bad" APK in a system partition, re-installing it to + // /data. Then, replace the APK in the system partition with a "good" one. + preparer.pushResourceFile(VERSION_TWO_ALT_KEY, productPath.toString()) + .reboot() + + // Install the "bad" APK to /data. This will also update package manager's XML records. + val versionTwoFile = HostUtils.copyResourceToHostFile( + VERSION_TWO_ALT_KEY, + tempFolder.newFile() + ) + assertThat(device.installPackage(versionTwoFile, true)).isNull() + assertThat(device.executeShellCommand("pm path ${TEST_PKG_NAME}")) + .doesNotContain(productPath.toString()) + + // "Restore" the system partition is to a good state with correct APK. + preparer.deleteFile(productPath.toString()) + .pushResourceFile(VERSION_ONE, productPath.toString()) + + // Verify that upon the next boot, the system detect the problem and remove the problematic + // APK in the /data. + preparer.reboot() + assertThat(device.executeShellCommand("pm path ${TEST_PKG_NAME}")) + .contains(productPath.toString()) + } + + @Test + fun detectApkTamperingAtBoot() { + // Set up the scenario where APK is tampered but not the v4 signature. First, inject a + // good APK as a system app. + preparer.pushResourceFile(VERSION_TWO_ALT_KEY, productPath.toString()) + .reboot() + + // Re-install the target APK to /data, with the corresponding .idsig from build time. + val versionTwoFile = HostUtils.copyResourceToHostFile( + VERSION_TWO_ALT_KEY, + tempFolder.newFile() + ) + assertThat(device.installPackage(versionTwoFile, true)).isNull() + val baseApkPath = device.executeShellCommand("pm path ${TEST_PKG_NAME}") + .lineSequence() + .first() + .replace("package:", "") + assertThat(baseApkPath).doesNotContain(productPath.toString()) + preparer.pushResourceFile(VERSION_TWO_ALT_KEY_IDSIG, baseApkPath.toString() + ".idsig") + + // Replace the APK in /data with a tampered version. Restore fs-verity and attributes. + RandomAccessFile(versionTwoFile, "rw").use { + // Skip the zip local file header to keep it valid. Tamper with the file name field and + // beyond, just so that it won't simply fail. + it.seek(30) + it.writeBytes("tamper") + } + device.executeShellCommand("touch ${TIMESTAMP_REFERENCE_FILE_PATH} -r $baseApkPath") + preparer.pushFile(versionTwoFile, baseApkPath) + device.executeShellCommand( + "cd ${baseApkPath.replace("base.apk", "")}" + + "&& chown system:system base.apk " + + "&& /data/local/tmp/fsverity_multilib enable base.apk" + + "&& touch base.apk -r ${TIMESTAMP_REFERENCE_FILE_PATH}" + ) + + // Verify that upon the next boot, the system detect the problem and remove the problematic + // APK in the /data. + preparer.reboot() + assertThat(device.executeShellCommand("pm path ${TEST_PKG_NAME}")) + .contains(productPath.toString()) + } +} diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp index 5cc3371a1a6e..bee7c4019fc1 100644 --- a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp +++ b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp @@ -66,3 +66,13 @@ android_test_helper_app { "src/**/*.kt", ], } + +android_test_helper_app { + name: "PackageManagerTestAppVersion2AltKey", + manifest: "AndroidManifestVersion2.xml", + srcs: [ + "src/**/*.kt", + ], + certificate: ":FrameworksServicesTests_keyset_A_cert", + v4_signature: true, +} diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java index 037637630b7a..184c976b86bf 100644 --- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java +++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java @@ -23,6 +23,7 @@ import static org.junit.Assert.fail; import android.content.Context; import android.graphics.FontListParser; +import android.graphics.fonts.FontFamily; import android.graphics.fonts.FontManager; import android.graphics.fonts.FontStyle; import android.graphics.fonts.FontUpdateRequest; @@ -330,7 +331,8 @@ public final class UpdatableFontDirTest { FontConfig.FontFamily family = new FontConfig.FontFamily( Arrays.asList(fooFont, barFont), null, - FontConfig.FontFamily.VARIANT_DEFAULT); + FontConfig.FontFamily.VARIANT_DEFAULT, + FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE); return new FontConfig(Collections.emptyList(), Collections.emptyList(), Collections.singletonList(new FontConfig.NamedFamilyList( @@ -491,7 +493,8 @@ public final class UpdatableFontDirTest { file, null, "bar", new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null); FontConfig.FontFamily family = new FontConfig.FontFamily( - Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT); + Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT, + FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE); return new FontConfig( Collections.emptyList(), Collections.emptyList(), @@ -644,7 +647,8 @@ public final class UpdatableFontDirTest { file, null, "test", new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null); FontConfig.FontFamily family = new FontConfig.FontFamily( - Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT); + Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT, + FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE); return new FontConfig(Collections.emptyList(), Collections.emptyList(), Collections.singletonList(new FontConfig.NamedFamilyList( Collections.singletonList(family), "sans-serif")), 0, 1); diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java index 3fc0e4fa4a2c..255cb6499d8a 100644 --- a/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java @@ -16,6 +16,8 @@ package com.android.server.inputmethod; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import android.os.LocaleList; @@ -386,4 +388,10 @@ public class LocaleUtilsTest { assertEquals(availableLocales.get(3), dest.get(0)); } } + + @Test + public void testGetLanguageFromLocaleString() { + assertThat(LocaleUtils.getLanguageFromLocaleString("en")).isEqualTo("en"); + assertThat(LocaleUtils.getLanguageFromLocaleString("en-US")).isEqualTo("en"); + } } diff --git a/services/tests/servicestests/src/com/android/server/timezone/OWNERS b/services/tests/servicestests/src/com/android/server/timezone/OWNERS index 61652604ee9f..d64cbcdc2814 100644 --- a/services/tests/servicestests/src/com/android/server/timezone/OWNERS +++ b/services/tests/servicestests/src/com/android/server/timezone/OWNERS @@ -1,2 +1,2 @@ -# Bug component: 24949 +# Bug component: 847766 include /services/core/java/com/android/server/timezone/OWNERS diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java index 72c3ebece242..e7ebd7db1023 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java @@ -43,6 +43,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.never; import android.annotation.NonNull; import android.annotation.Nullable; @@ -58,6 +59,7 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; import android.os.LocaleList; +import android.os.PowerManagerInternal; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.view.Display; @@ -380,12 +382,12 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { // The top app should not change while sleeping. assertEquals(topActivity.app, mAtm.mInternal.getTopApp()); - mAtm.startLaunchPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY + mAtm.startPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY | ActivityTaskManagerService.POWER_MODE_REASON_UNKNOWN_VISIBILITY); assertEquals(ActivityManager.PROCESS_STATE_TOP, mAtm.mInternal.getTopProcessState()); // Because there is no unknown visibility record, the state will be restored if other // reasons are all done. - mAtm.endLaunchPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY); + mAtm.endPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY); assertEquals(ActivityManager.PROCESS_STATE_TOP_SLEEPING, mAtm.mInternal.getTopProcessState()); @@ -410,6 +412,37 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { } @Test + public void testSetPowerMode() { + // Depends on the mocked power manager set in SystemServicesTestRule#setUpLocalServices. + mAtm.onInitPowerManagement(); + + // Apply different power modes according to the reasons. + mAtm.startPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY); + verify(mWm.mPowerManagerInternal).setPowerMode( + PowerManagerInternal.MODE_LAUNCH, true); + mAtm.startPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY); + verify(mWm.mPowerManagerInternal).setPowerMode( + PowerManagerInternal.MODE_DISPLAY_CHANGE, true); + + // If there is unknown visibility launching app, the launch power mode won't be canceled + // even if REASON_START_ACTIVITY is cleared. + mAtm.startPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_UNKNOWN_VISIBILITY); + mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(mock(ActivityRecord.class)); + mAtm.endPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY); + verify(mWm.mPowerManagerInternal, never()).setPowerMode( + PowerManagerInternal.MODE_LAUNCH, false); + + mDisplayContent.mUnknownAppVisibilityController.clear(); + mAtm.endPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY); + verify(mWm.mPowerManagerInternal).setPowerMode( + PowerManagerInternal.MODE_LAUNCH, false); + + mAtm.endPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY); + verify(mWm.mPowerManagerInternal).setPowerMode( + PowerManagerInternal.MODE_DISPLAY_CHANGE, false); + } + + @Test public void testSupportsMultiWindow_resizable() { final ActivityRecord activity = new ActivityBuilder(mAtm) .setCreateTask(true) diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 75716b913cee..241f7d8937f2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -1081,6 +1081,7 @@ public class TransitionTests extends WindowTestsBase { makeWindowVisible(windows); mDisplayContent.getDisplayPolicy().addWindowLw(statusBar, statusBar.mAttrs); mDisplayContent.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs); + mDisplayContent.mTransitionController.setSyncEngine(createTestBLASTSyncEngine()); final TestTransitionPlayer player = registerTestTransitionPlayer(); mDisplayContent.getDisplayRotation().setRotation(mDisplayContent.getRotation() + 1); diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 1ddb5c0e43ae..18c960e3ebbc 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -2607,11 +2607,12 @@ public class UsageStatsService extends SystemService implements @Override public void reportChooserSelection(@NonNull String packageName, int userId, @NonNull String contentType, String[] annotations, @NonNull String action) { - // A valid package name, content type, and action must be provided for these events - Objects.requireNonNull(packageName); - Objects.requireNonNull(contentType); - Objects.requireNonNull(action); - if (contentType.isBlank() || action.isBlank()) { + if (packageName == null) { + throw new IllegalArgumentException("Package selection must not be null."); + } + // A valid contentType and action must be provided for chooser selection events. + if (contentType == null || contentType.isBlank() + || action == null || action.isBlank()) { return; } diff --git a/tests/Input/src/com/android/server/input/InputShellCommandTest.java b/tests/Input/src/com/android/server/input/InputShellCommandTest.java new file mode 100644 index 000000000000..f4845a518b20 --- /dev/null +++ b/tests/Input/src/com/android/server/input/InputShellCommandTest.java @@ -0,0 +1,167 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.input; + +import static android.view.InputDevice.SOURCE_MOUSE; +import static android.view.InputDevice.SOURCE_ROTARY_ENCODER; +import static android.view.MotionEvent.ACTION_SCROLL; +import static android.view.MotionEvent.AXIS_HSCROLL; +import static android.view.MotionEvent.AXIS_SCROLL; +import static android.view.MotionEvent.AXIS_VSCROLL; +import static android.view.MotionEvent.AXIS_X; +import static android.view.MotionEvent.AXIS_Y; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import android.os.Binder; +import android.view.InputEvent; +import android.view.MotionEvent; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.FileDescriptor; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; + +/** + * Build/Install/Run: + * atest InputShellCommandTest + */ +@RunWith(AndroidJUnit4.class) +public class InputShellCommandTest { + private TestInputEventInjector mInputEventInjector = new TestInputEventInjector(); + + private InputShellCommand mCommand; + + @Before + public void setUp() throws Exception { + mCommand = new InputShellCommand(mInputEventInjector); + } + + @Test + public void testScroll_withPointerSource_noAxisOption() { + runCommand("mouse scroll 2 -3"); + + MotionEvent event = (MotionEvent) getSingleInjectedInputEvent(); + + assertSourceAndAction(event, SOURCE_MOUSE, ACTION_SCROLL); + assertAxisValues(event, Map.of(AXIS_X, 2f, AXIS_Y, -3f)); + } + + @Test + public void testScroll_withPointerSource_withScrollAxisOptions() { + runCommand("mouse scroll 1 -2 --axis HSCROLL,3 --axis VSCROLL,1.7 --axis SCROLL,-4"); + + MotionEvent event = (MotionEvent) getSingleInjectedInputEvent(); + + assertSourceAndAction(event, SOURCE_MOUSE, ACTION_SCROLL); + assertAxisValues( + event, + Map.of( + AXIS_X, 1f, + AXIS_Y, -2f, + AXIS_HSCROLL, 3f, + AXIS_VSCROLL, 1.7f, + AXIS_SCROLL, -4f)); + } + + @Test + public void testScroll_withNonPointerSource_noAxisOption() { + runCommand("rotaryencoder scroll"); + + MotionEvent event = (MotionEvent) getSingleInjectedInputEvent(); + + assertSourceAndAction(event, SOURCE_ROTARY_ENCODER, ACTION_SCROLL); + } + + @Test + public void testScroll_withNonPointerSource_withScrollAxisOptions() { + runCommand("rotaryencoder scroll --axis HSCROLL,3 --axis VSCROLL,1.7 --axis SCROLL,-4"); + + MotionEvent event = (MotionEvent) getSingleInjectedInputEvent(); + + assertSourceAndAction(event, SOURCE_ROTARY_ENCODER, ACTION_SCROLL); + assertAxisValues(event, Map.of(AXIS_HSCROLL, 3f, AXIS_VSCROLL, 1.7f, AXIS_SCROLL, -4f)); + } + + @Test + public void testDefaultScrollSource() { + runCommand("scroll --axis SCROLL,-4"); + + MotionEvent event = (MotionEvent) getSingleInjectedInputEvent(); + + assertSourceAndAction(event, SOURCE_ROTARY_ENCODER, ACTION_SCROLL); + assertAxisValues(event, Map.of(AXIS_SCROLL, -4f)); + } + + @Test + public void testInvalidScrollCommands() { + runCommand("scroll --sdaxis SCROLL,-4"); // invalid option + runCommand("scroll --axis MYAXIS,-4"); // invalid axis + runCommand("scroll --AXIS SCROLL,-4"); // invalid axis option key + runCommand("scroll --axis SCROLL,-4abc"); // invalid axis value + + assertThat(mInputEventInjector.mInjectedEvents).isEmpty(); + } + + private InputEvent getSingleInjectedInputEvent() { + assertThat(mInputEventInjector.mInjectedEvents).hasSize(1); + return mInputEventInjector.mInjectedEvents.get(0); + } + + private void assertSourceAndAction(MotionEvent event, int source, int action) { + assertThat(event.getSource()).isEqualTo(source); + assertThat(event.getAction()).isEqualTo(action); + } + + private void assertAxisValues(MotionEvent event, Map<Integer, Float> expectedValues) { + for (var entry : expectedValues.entrySet()) { + final int axis = entry.getKey(); + final float expectedValue = entry.getValue(); + final float axisValue = event.getAxisValue(axis); + assertWithMessage( + String.format( + "Expected [%f], found [%f] for axis %s", + expectedValue, + axisValue, + MotionEvent.axisToString(axis))) + .that(axisValue).isEqualTo(expectedValue); + } + } + + private void runCommand(String cmd) { + mCommand.exec( + new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(), + cmd.split(" ") /* args */); + } + + private static class TestInputEventInjector implements BiConsumer<InputEvent, Integer> { + List<InputEvent> mInjectedEvents = new ArrayList<>(); + + @Override + public void accept(InputEvent event, Integer injectMode) { + mInjectedEvents.add(event); + } + } +} |