Merge "Modify usagestats event processing thread" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index ab20edc..95b6155 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -39,6 +39,7 @@
":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 @@
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 f017a47..45e70719 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -62,15 +62,6 @@
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 bf6216c..965093c 100644
--- a/api/OWNERS
+++ b/api/OWNERS
@@ -3,7 +3,10 @@
# 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 e5e0ad3..2d9c988 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -359,13 +359,15 @@
],
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 43aaee8..2b50e38 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -15224,6 +15224,7 @@
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 e997259..358c8e7 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -12731,16 +12731,11 @@
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 @@
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 b11a686..4fb7b6b 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -19,8 +19,12 @@
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 @@
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 @@
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 951905b..7809048 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 0000000..53fd74b
--- /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 46f2319..5e64c83 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 baed7f9..39800f7 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -637,15 +637,15 @@
/**
* 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 @@
/**
* 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 a9eb672..1307dfc 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -484,7 +484,6 @@
/**
* 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 @@
* <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 @@
/**
* 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 8afd6de..fec67b9 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -315,6 +315,13 @@
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 1294f98..5626b94 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -12,4 +12,11 @@
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 3b757d6..33978be 100644
--- a/core/java/android/service/controls/Control.java
+++ b/core/java/android/service/controls/Control.java
@@ -50,7 +50,7 @@
* 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 @@
* 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 @@
* <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 fce87db..0272bb9 100644
--- a/core/java/android/service/controls/ControlsProviderService.java
+++ b/core/java/android/service/controls/ControlsProviderService.java
@@ -155,7 +155,7 @@
* 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 10f526d..4e38222 100644
--- a/core/java/android/service/controls/actions/ControlAction.java
+++ b/core/java/android/service/controls/actions/ControlAction.java
@@ -154,7 +154,7 @@
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 3902d6a..0dd950d 100644
--- a/core/java/android/service/controls/templates/ControlTemplate.java
+++ b/core/java/android/service/controls/templates/ControlTemplate.java
@@ -137,7 +137,7 @@
}
/**
- * 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 0000000..b0ce1db
--- /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 9dca77e..31aeb9c 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 @@
* 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 @@
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 @@
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 @@
//@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 @@
* 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 @@
// String fieldNameToString() { ... }
return "HotwordTrainingData { " +
- "trainingAudios = " + mTrainingAudios + ", " +
+ "trainingAudioList = " + mTrainingAudioList + ", " +
"timeoutStage = " + mTimeoutStage +
" }";
}
@@ -221,7 +152,7 @@
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 @@
// 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 @@
// 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 @@
// 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 @@
@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 @@
* 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 @@
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 @@
}
@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 49e00d6..7befbfb 100644
--- a/core/java/android/service/watchdog/ExplicitHealthCheckService.java
+++ b/core/java/android/service/watchdog/ExplicitHealthCheckService.java
@@ -151,7 +151,7 @@
*/
@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 cb488b0..c585734 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -26,6 +26,7 @@
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 @@
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 @@
* @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 @@
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 @@
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 @@
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 cdf5eec3..70e1896 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -2197,6 +2197,9 @@
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 @@
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 @@
* 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 @@
/** @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 f421351..afa3157 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -3858,7 +3858,8 @@
mPendingTransitions.clear();
}
- handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction);
+ handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction,
+ "view not visible");
} else if (cancelAndRedraw) {
mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener
? "predraw_" + mAttachInfo.mTreeObserver.getLastDispatchOnPreDrawCanceledReason()
@@ -3873,7 +3874,8 @@
mPendingTransitions.clear();
}
if (!performDraw(mActiveSurfaceSyncGroup)) {
- handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction);
+ handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction,
+ mLastPerformDrawSkippedReason);
}
}
@@ -4665,6 +4667,10 @@
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 @@
return false;
}
- final boolean fullRedrawNeeded =
- mFullRedrawNeeded || surfaceSyncGroup != null || mHasPendingTransactions;
+ final boolean fullRedrawNeeded = mFullRedrawNeeded || surfaceSyncGroup != null;
mFullRedrawNeeded = false;
mIsDrawing = true;
@@ -4748,7 +4753,8 @@
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 @@
}
if (!usingAsyncReport) {
- handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction);
+ handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction,
+ "no async report");
}
if (mPerformContentCapture) {
@@ -4772,13 +4779,19 @@
}
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 @@
mAdded = false;
AnimationHandler.removeRequestor(this);
}
- handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction);
+ handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction,
+ "shutting down VRI");
WindowManagerGlobal.getInstance().doRemoveView(this);
}
@@ -11362,15 +11376,15 @@
@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 f543cab..334c2b77 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -255,6 +255,16 @@
// 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 @@
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 6cf185a..89fa83e 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -3443,7 +3443,7 @@
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 0000000..7e06f87
--- /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 195565c..33db671 100644
--- a/core/java/android/view/textclassifier/TextClassifierEvent.java
+++ b/core/java/android/view/textclassifier/TextClassifierEvent.java
@@ -551,8 +551,8 @@
* 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 b7e13dd..52760f7 100644
--- a/core/java/android/view/translation/TranslationCapability.java
+++ b/core/java/android/view/translation/TranslationCapability.java
@@ -207,7 +207,7 @@
/**
* 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 5aad823..99544e8 100644
--- a/core/java/android/view/translation/TranslationManager.java
+++ b/core/java/android/view/translation/TranslationManager.java
@@ -56,7 +56,7 @@
* 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 027edc2..ff11ffa 100644
--- a/core/java/android/view/translation/TranslationRequest.java
+++ b/core/java/android/view/translation/TranslationRequest.java
@@ -27,7 +27,7 @@
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 b77f2e2..3362fc0 100644
--- a/core/java/android/view/translation/TranslationResponse.java
+++ b/core/java/android/view/translation/TranslationResponse.java
@@ -20,7 +20,6 @@
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.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 9dff2d5..18a240d 100644
--- a/core/java/android/view/translation/TranslationResponseValue.java
+++ b/core/java/android/view/translation/TranslationResponseValue.java
@@ -27,7 +27,7 @@
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 a41749a..54b8ac2 100644
--- a/core/java/android/view/translation/ViewTranslationRequest.java
+++ b/core/java/android/view/translation/ViewTranslationRequest.java
@@ -33,7 +33,7 @@
/**
* 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 d993114..134ff5a 100644
--- a/core/java/android/view/translation/ViewTranslationResponse.java
+++ b/core/java/android/view/translation/ViewTranslationResponse.java
@@ -33,7 +33,7 @@
/**
* 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 0000000..9d711a2
--- /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 0000000..6c4d48d
--- /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 0000000..8872f64
--- /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 d46f762..4dd5889 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 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
.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 0000000..46f476f
--- /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 1e97fce..02e032b 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" />
+ <family name="sans-serif-condensed" varFamilyType="2">
+ <font>Roboto-Regular.ttf
<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" />
- <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" />
+ <family name="roboto-flex" varFamilyType="2">
+ <font>RobotoFlex-Regular.ttf
<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" />
- <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">
+ <family lang="und-Ethi" varFamilyType="1">
+ <font postScriptName="NotoSansEthiopic-Regular">
NotoSansEthiopic-VF.ttf
- <axis tag="wght" stylevalue="400"/>
</font>
- <font weight="500" style="normal" 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">
+ <family lang="und-Armn" varFamilyType="1">
+ <font 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">
- 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">
+ <family lang="und-Deva" variant="elegant" varFamilyType="1">
+ <font 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">
- 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">
+ <family lang="und-Deva" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansDevanagariUI-Regular">
NotoSansDevanagariUI-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" 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">
+ <family lang="und-Guru" variant="compact" varFamilyType="1">
+ <font 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">
- NotoSansGurmukhiUI-VF.ttf
- <axis tag="wght" stylevalue="700"/>
</font>
</family>
- <family lang="und-Taml" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoSansTamil-Regular">
+ <family lang="und-Taml" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansTamil-Regular">
NotoSansTamil-VF.ttf
- <axis tag="wght" stylevalue="400"/>
</font>
- <font weight="500" style="normal" 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">
+ <family lang="und-Taml" variant="compact" varFamilyType="1">
+ <font 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">
- 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">
+ <family lang="und-Mlym" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansMalayalam-Regular">
NotoSansMalayalam-VF.ttf
- <axis tag="wght" stylevalue="400"/>
</font>
- <font weight="500" style="normal" 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">
+ <family lang="und-Mlym" variant="compact" varFamilyType="1">
+ <font 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">
- NotoSansMalayalamUI-VF.ttf
- <axis tag="wght" stylevalue="700"/>
</font>
</family>
- <family lang="und-Beng" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoSansBengali-Regular">
+ <family lang="und-Beng" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansBengali-Regular">
NotoSansBengali-VF.ttf
- <axis tag="wght" stylevalue="400"/>
</font>
- <font weight="500" style="normal" 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">
+ <family lang="und-Beng" variant="compact" varFamilyType="1">
+ <font 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">
- NotoSansBengaliUI-VF.ttf
- <axis tag="wght" stylevalue="700"/>
</font>
</family>
- <family lang="und-Telu" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoSansTelugu-Regular">
+ <family lang="und-Telu" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansTelugu-Regular">
NotoSansTelugu-VF.ttf
- <axis tag="wght" stylevalue="400"/>
</font>
- <font weight="500" style="normal" 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">
+ <family lang="und-Telu" variant="compact" varFamilyType="1">
+ <font 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">
- 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">
+ <family lang="und-Knda" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansKannadaUI-Regular">
NotoSansKannadaUI-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" 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">
+ <family lang="und-Sinh" variant="compact" varFamilyType="1">
+ <font 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">
- 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">
+ <family lang="und-Adlm" varFamilyType="1">
+ <font postScriptName="NotoSansAdlam-Regular">
NotoSansAdlam-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" 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">
+ <family lang="und-Medf" varFamilyType="1">
+ <font 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">
- NotoSansMedefaidrin-VF.ttf
- <axis tag="wght" stylevalue="700"/>
</font>
</family>
- <family lang="und-Soyo">
- <font weight="400" style="normal" postScriptName="NotoSansSoyombo-Regular">
+ <family lang="und-Soyo" varFamilyType="1">
+ <font postScriptName="NotoSansSoyombo-Regular">
NotoSansSoyombo-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" 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">
+ <family lang="und-Hmnp" varFamilyType="1">
+ <font 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">
- NotoSerifNyiakengPuachueHmong-VF.ttf
- <axis tag="wght" stylevalue="700"/>
</font>
</family>
- <family lang="und-Yezi">
- <font weight="400" style="normal" postScriptName="NotoSerifYezidi-Regular">
+ <family lang="und-Yezi" varFamilyType="1">
+ <font postScriptName="NotoSerifYezidi-Regular">
NotoSerifYezidi-VF.ttf
- <axis tag="wght" stylevalue="400"/>
- </font>
- <font weight="500" style="normal" 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 354f10a..87cb942 100644
--- a/data/keyboards/Vendor_0957_Product_0001.kl
+++ b/data/keyboards/Vendor_0957_Product_0001.kl
@@ -47,7 +47,6 @@
# 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 1ff5a3d..250362b 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -479,7 +479,8 @@
* 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 @@
* 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 @@
* 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 @@
* 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 5c06577..dcfff62 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 @@
* Prevent garbage collection.
*/
/*package*/ Bitmap mBitmap;
+ private Gainmap mOverrideGainmap;
private int mTileX;
private int mTileY;
@@ -173,6 +178,24 @@
}
/**
+ * 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 @@
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 @@
}
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 674246a..735bc18 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.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 @@
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 @@
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 5e41105..4c75356 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -18,7 +18,10 @@
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 libcore.util.NativeAllocationRegistry;
+import java.lang.annotation.Retention;
import java.util.ArrayList;
import java.util.Set;
@@ -184,32 +188,59 @@
}
/**
+ * 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 @@
*/
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 9810022..d4e35b3 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -121,7 +121,8 @@
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 @@
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 @@
@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 @@
}
}
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 @@
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 @@
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 f259902..dddcbd4 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 @@
* <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 @@
* 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 85aaa8e..4206d93 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 @@
* 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 9d8b9a6..e1dea3b 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 @@
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 26b5a50..63cdb4f 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.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 @@
}
/** 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 ff38b7e..e734300 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 @@
/** 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 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 @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 9f9854e..11aa0546 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 @@
@ShellMainThread Handler mainHandler,
@ShellMainThread Choreographer mainChoreographer,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
ShellController shellController,
@@ -213,6 +214,7 @@
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 ccffa02..664d4491 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_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.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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 4ea14f4..5e2c61b 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.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 @@
/** 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 @@
/** 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 @@
/** 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 @@
* {@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 @@
@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 @@
/** 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 @@
@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 @@
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 @@
/** 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 @@
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 @@
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 @@
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 a68b41d..3e06d2d 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.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 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 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 afa2754..780bbb5 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.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 @@
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 @@
Handler mainHandler,
Choreographer mainChoreographer,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
ShellController shellController,
@@ -148,6 +152,7 @@
mainHandler,
mainChoreographer,
shellInit,
+ shellCommandHandler,
taskOrganizer,
displayController,
shellController,
@@ -167,6 +172,7 @@
Handler mainHandler,
Choreographer mainChoreographer,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
ShellController shellController,
@@ -189,7 +195,7 @@
mTransitions = transitions;
mDesktopTasksController = desktopTasksController;
mRecentsTransitionHandler = recentsTransitionHandler;
-
+ mShellCommandHandler = shellCommandHandler;
mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
mInputMonitorFactory = inputMonitorFactory;
mTransactionFactory = transactionFactory;
@@ -206,6 +212,7 @@
onRecentsTransitionStarted(transition);
}
});
+ mShellCommandHandler.addDumpCallback(this::dump, this);
}
@Override
@@ -593,6 +600,15 @@
super.dispose();
}
+ @Override
+ public String toString() {
+ return "EventReceiver"
+ + "{"
+ + "tasksOnDisplay="
+ + mTasksOnDisplay
+ + "}";
+ }
+
private void incrementTaskNumber() {
mTasksOnDisplay++;
}
@@ -981,6 +997,15 @@
&& 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 84ec6b3..380b59e 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 @@
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 @@
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 0000000..fe26110
--- /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 00d70a7..8eaf5a0 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.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 @@
@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 @@
mockMainHandler,
mockMainChoreographer,
shellInit,
+ mockShellCommandHandler,
mockTaskOrganizer,
mockDisplayController,
mockShellController,
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 2c13ceb..a952be0 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -65,21 +65,41 @@
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 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 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 8986e52..98ad22c 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 @@
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 0000000..3471070
--- /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 555a86f..99dc9ec 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 @@
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 6de3e7d..02ee77b 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
- }
-
- 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.
+ 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
+
+ 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 6011a1c..ed98f3e 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 @@
* 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 0000000..1cce3ba
--- /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 7c81fd0..e8e4033 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 @@
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 a11017b..1e8f83d 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 @@
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 @@
): 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 c885ec4..c87cfd3 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.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 @@
}
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 @@
@Composable
fun SinglePasswordScreen(
- email: String,
+ passwordUiModel: PasswordUiModel,
onCancelClick: () -> Unit,
onOKClick: () -> Unit,
columnState: ScalingLazyColumnState,
@@ -113,7 +113,7 @@
},
accountContent = {
PasswordRow(
- email = email,
+ email = passwordUiModel.email,
modifier = Modifier.padding(top = 10.dp),
)
},
@@ -134,7 +134,7 @@
@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 9b06622..3167e67 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.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 @@
class SinglePasswordScreenViewModel(
private val requestRepository: RequestRepository,
+ private val passwordRepository: PasswordRepository,
) : ViewModel() {
private var initializeCalled = false
@@ -87,15 +85,8 @@
}
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 @@
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}}")
+ viewModelScope.launch {
+ passwordRepository.selectPassword(
+ password = password,
+ request = requestGet,
+ resultCode = resultCode,
+ resultData = resultData
+ )
- 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
+ _uiState.value = SinglePasswordScreenUiState.Completed
+ }
}
companion object {
@@ -135,6 +117,7 @@
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 70832f5..437f8af 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -5,4 +5,11 @@
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 44c4105..bb2fbf7 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.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 @@
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 @@
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 @@
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
+ colors: ButtonColors = textButtonColors(),
content: @Composable RowScope.() -> Unit,
) {
androidx.compose.material3.TextButton(
@@ -78,10 +85,11 @@
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 a61e959..a9944f7 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.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.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.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.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 @@
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 @@
}
}
- 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 @@
/** 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 @@
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 @@
)
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 @@
modifier = modifier,
) {
UserSwitcher(
+ viewModel = viewModel,
modifier = Modifier.fillMaxWidth().weight(1f),
)
Bouncer(
@@ -343,3 +493,36 @@
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 cb76ad7..651594c 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 @@
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 0000000..d4916b4
--- /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 0000000..7bc120e
--- /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 6dd44fb..1c7e997 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"
+ <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" />
+
+ <TextView
+ android:layout_width="0dp"
+ android:id="@+id/bluetooth_device_name"
+ style="@style/BluetoothTileDialog.DeviceName"
android:paddingStart="20dp"
- android:baselineAligned="false">
+ 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" />
- <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" />
+ <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/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" />
+ <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" />
- <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" />
-
- <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 6cdd15e..81101d8 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 4b23795..67b7052 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -552,6 +552,10 @@
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 c09e68d..f94f8c5 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 @@
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 0cbfb68..7f3b794 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 66c6162..55bc653 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 @@
*
* @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 c98cf31..2cb98d8 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.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 @@
),
)
+ val isEmergencyButtonVisible: Boolean
+ get() = telephonyInteractor.hasTelephonyRadio
+
init {
if (flags.isEnabled()) {
applicationScope.launch {
@@ -200,4 +249,40 @@
*/
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 52adf54..d301085 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 @@
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 ea3ddac..f6e0296 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 @@
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 1057852..44b5e99 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 @@
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 c79952e..8587329 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 @@
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 2a69ec5..fde92b8 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 @@
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 @@
*/
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 @@
// 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 @@
}
/**
- * 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 1761ca8..f500017 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 @@
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 7e826b8..7678f4d 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 @@
// 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 @@
} 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 @@
// 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 b2e436c..8bf2bc3 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 @@
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 @@
.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 0000000..02d1471
--- /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 053727a..ac4ad39 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 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.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 @@
chipbarCoordinator: ChipbarCoordinator,
keyguardStateController: KeyguardStateController,
shadeInteractor: ShadeInteractor,
+ clockControllerProvider: Provider<ClockController>?,
): DisposableHandle {
val disposableHandle =
view.repeatWhenAttached {
@@ -83,10 +86,37 @@
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 @@
}
}
}
+ 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 864e345..231eeb5 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.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.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 @@
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 1eeb017..e8df1a6 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 @@
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 @@
defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection,
splitShadeGuidelines: SplitShadeGuidelines,
aodNotificationIconsSection: AodNotificationIconsSection,
+ aodBurnInSection: AodBurnInSection,
) : KeyguardBlueprint {
override val id: String = DEFAULT
@@ -66,6 +68,7 @@
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 a9885fc..ce76f56 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 @@
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 @@
splitShadeGuidelines: SplitShadeGuidelines,
defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection,
aodNotificationIconsSection: AodNotificationIconsSection,
+ aodBurnInSection: AodBurnInSection,
) : KeyguardBlueprint {
override val id: String = SHORTCUTS_BESIDE_UDFPS
@@ -61,6 +63,7 @@
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 0000000..09caf45
--- /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 c49af4d..89835fe 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 @@
*/
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 @@
}
}
- 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 1943b34..67531ad 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.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 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 @@
@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 @@
/**
* 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 @@
/**
* 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 0000000..6575cdd
--- /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 0000000..62d49fe
--- /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 0000000..b0c2f8c
--- /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 19012e2..fa18b35b 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 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 @@
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 @@
}
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 128c237..051eeb0 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.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 @@
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 @@
* 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 @@
}
}
+ @Override
public int getUser() {
return mUser;
}
+ @Override
public ComponentName getComponent() {
return mComponent;
}
@@ -265,6 +269,7 @@
return super.populate(logMaker).setComponentName(mComponent);
}
+ @Override
public Tile getQsTile() {
// TODO(b/191145007) Move to background thread safely
updateDefaultTileAndIcon();
@@ -276,6 +281,7 @@
*
* @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 @@
mTile.setState(tile.getState());
}
+ @Override
public void onDialogShown() {
mIsShowingDialog = true;
}
+ @Override
public void onDialogHidden() {
mIsShowingDialog = false;
try {
@@ -507,6 +515,7 @@
return mComponent.getPackageName();
}
+ @Override
public void startUnlockAndRun() {
mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
try {
@@ -518,8 +527,10 @@
/**
* 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 0000000..9e02320
--- /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 fc24022..acee8e9 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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
}
@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 @@
@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 @@
@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 @@
@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 @@
}
@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 @@
};
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 38fe34e..42d3f81 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 @@
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 @@
/** 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 8b2c3de..c91ed13 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.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 0000000..0d15a5b
--- /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 0000000..70a683b
--- /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 58a335e..2114751 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.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.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 @@
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 @@
@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 @@
tileDataInteractor,
mapper,
disabledByPolicyInteractor,
+ falsingManager,
+ qsTileAnalytics,
+ qsTileLogger,
backgroundDispatcher,
CoroutineScope(SupervisorJob())
)
@@ -98,8 +111,10 @@
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 @@
@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 @@
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 @@
currentLifeState = lifecycle
}
- private fun createTileDataFlow(): SharedFlow<DATA_TYPE> =
+ private fun createTileDataFlow(): SharedFlow<DataWithTrigger<DATA_TYPE>> =
userIds
.flatMapLatest { userId ->
merge(
@@ -180,7 +210,7 @@
request.trigger.tileData as DATA_TYPE,
)
}
- dataFlow
+ dataFlow.map { DataWithTrigger(it, request.trigger) }
}
.flowOn(backgroundDispatcher)
.shareIn(
@@ -193,21 +223,53 @@
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 efad9ec..8957fc3 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 @@
@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 6815a73..8ae2dc2 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.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 @@
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 @@
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 @@
setupToggle()
setupRecyclerView()
+ subtitleTextView.text = context.getString(subtitleResIdInitialValue)
doneButton.setOnClickListener { dismiss() }
seeAllText.setOnClickListener { bluetoothTileDialogCallback.onSeeAllClicked(it) }
pairNewDeviceText.setOnClickListener {
@@ -91,7 +97,6 @@
}
}
- // 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 @@
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 @@
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 @@
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 @@
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 @@
}
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 012484f..97e1783 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.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.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 @@
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 @@
}
.launchIn(this)
- deviceItemInteractor.updateDeviceItemsFlow
+ deviceItemInteractor.deviceItemUpdateRequest
.onEach {
updateDeviceItemJob?.cancel()
updateDeviceItemJob = launch {
@@ -97,7 +112,7 @@
}
.launchIn(this)
- deviceItemInteractor.deviceItemFlow
+ deviceItemInteractor.deviceItemUpdate
.filterNotNull()
.onEach {
dialog!!.onDeviceItemUpdated(
@@ -109,17 +124,13 @@
.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 @@
private fun createBluetoothTileDialog(context: Context): BluetoothTileDialog {
return BluetoothTileDialog(
bluetoothStateInteractor.isBluetoothEnabled,
+ getSubtitleResId(bluetoothStateInteractor.isBluetoothEnabled),
this@BluetoothTileDialogViewModel,
uiEventLogger,
context
@@ -175,6 +187,13 @@
)
}
}
+
+ 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 03ae5e8..50eaf38 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 @@
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 @@
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 a16a9f1..8c22614 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.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 @@
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 @@
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 @@
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 fcd0ce6..14d24f9 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 @@
@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 @@
withContext(backgroundDispatcher) {
val mostRecentlyConnectedDevices = bluetoothAdapter?.mostRecentlyConnectedDevices
- mutableDeviceItemFlow.value =
+ mutableDeviceItemUpdate.value =
bluetoothTileDialogRepository.cachedDevices
.mapNotNull { cachedDevice ->
deviceItemFactoryList
@@ -143,28 +144,20 @@
)
}
- 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 1a6cf99..4a3bcae 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 @@
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 3501b6b..bd43307 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 @@
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 a821729..d3d38e5c 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 @@
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 249c831..e2de37f 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 @@
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 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 0000000..8136de9
--- /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 0000000..e0c30e5
--- /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 a54687c..20241c3 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.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.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 @@
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 @@
return
}
if (screenOffAnimationController.shouldAnimateAodIcons()) {
- aodIcons!!.translationY = -aodIconAppearTranslation.toFloat()
+ if (!statusViewMigrated) {
+ aodIcons!!.translationY = -aodIconAppearTranslation.toFloat()
+ }
aodIcons!!.alpha = 0f
animateInAodIconTranslation()
aodIcons!!
@@ -286,7 +289,9 @@
.start()
} else {
aodIcons!!.alpha = 1.0f
- aodIcons!!.translationY = 0f
+ if (!statusViewMigrated) {
+ aodIcons!!.translationY = 0f
+ }
}
}
@@ -598,12 +603,14 @@
}
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 @@
}
} 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 4e81d0c..69453c6 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 @@
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 @@
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 @@
}
/**
- * 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 @@
}
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 @@
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 @@
} 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 430b0e9..63591d7 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.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.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 @@
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 @@
@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 @@
return
}
+
if (configFaceAuthSupportedPosture != DEVICE_POSTURE_UNKNOWN) {
devicePostureController.addCallback { posture ->
if (postureState != posture) {
@@ -137,7 +147,7 @@
}
}
- 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 @@
}
})
- 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 7efa705..58126ae 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 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 @@
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 9cf9714..2960520 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 @@
new BatteryController.BatteryStateChangeCallback() {
@Override
public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
- mView.onBatteryLevelChanged(charging);
+ mView.onBatteryChargingChanged(charging);
}
};
@@ -430,11 +430,18 @@
/** 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 @@
/** 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 @@
/** 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 4bfce4c..660aa3f 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 @@
private int mAodIconTint;
private boolean mAodIconsVisible;
private boolean mShowLowPriority = true;
+ private boolean mIsStatusViewMigrated = false;
@VisibleForTesting
final NotificationListener.NotificationSettingsListener mSettingsListener =
@@ -158,7 +159,7 @@
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 @@
return;
}
if (mScreenOffAnimationController.shouldAnimateAodIcons()) {
- mAodIcons.setTranslationY(-mAodIconAppearTranslation);
+ if (!mIsStatusViewMigrated) {
+ mAodIcons.setTranslationY(-mAodIconAppearTranslation);
+ }
mAodIcons.setAlpha(0);
animateInAodIconTranslation();
mAodIcons.animate()
@@ -567,16 +570,20 @@
.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 @@
}
} 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 c63ef9e..6988e21 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 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 @@
) {
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 ddfed87..5da01e2 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 @@
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 @@
!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 9c38dc0f..3b300249 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 @@
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 @@
class TelephonyRepositoryImpl
@Inject
constructor(
+ @Application private val applicationContext: Context,
private val manager: TelephonyListenerManager,
) : TelephonyRepository {
@Annotation.CallState
@@ -53,4 +60,7 @@
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 86ca33d..4642f55 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 @@
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 18ae107..71352ef 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 @@
@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
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/UserSwitcherRepository.kt
rename to packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
index 5fa75ad..dc7fadd 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.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.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 @@
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 @@
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 61952ba..d3f83b1 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 @@
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 6b5679a..8e54eb7 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 @@
}
@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 @@
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 6308269..5eab2fc 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.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 @@
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 @@
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 7f4755d..7940b45 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.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 @@
@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 @@
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 a14a1c5..71688db 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.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 @@
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 @@
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 @@
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 @@
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 0000000..2c4e10e
--- /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 0000000..4401e0d
--- /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 89fa55b3..8b66040 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 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 @@
@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 @@
@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 @@
@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 @@
@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 7157cce..a0ff2ab 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 @@
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 @@
bluetoothTileDialogViewModel.showDialog(context, null)
assertThat(bluetoothTileDialogViewModel.dialog).isNotNull()
- verify(deviceItemInteractor).deviceItemFlow
+ verify(deviceItemInteractor).deviceItemUpdate
}
}
@@ -153,7 +154,7 @@
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 3451902..92c7326 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 @@
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 07a95ae..3593075 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 @@
interactor.updateDeviceItems(mContext)
- assertThat(interactor.deviceItemFlow.value).isEmpty()
+ assertThat(interactor.deviceItemUpdate.value).isEmpty()
}
}
@@ -123,7 +123,7 @@
interactor.updateDeviceItems(mContext)
- assertThat(interactor.deviceItemFlow.value).isEmpty()
+ assertThat(interactor.deviceItemUpdate.value).isEmpty()
}
}
@@ -137,8 +137,8 @@
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 @@
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 @@
interactor.updateDeviceItems(mContext)
- assertThat(interactor.deviceItemFlow.value).isEqualTo(listOf(deviceItem2, deviceItem1))
+ assertThat(interactor.deviceItemUpdate.value)
+ .isEqualTo(listOf(deviceItem2, deviceItem1))
}
}
@@ -201,7 +202,8 @@
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 9024c6c..4760dfa 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 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 @@
@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 @@
@Before
fun setup() {
+ MockitoAnnotations.initMocks(this)
underTest = createViewModel(testScope)
}
@@ -79,6 +104,9 @@
QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {}
},
fakeDisabledByPolicyInteractor,
+ fakeFalsingManager,
+ qsTileAnalytics,
+ qsTileLogger,
testCoroutineDispatcher,
scope.backgroundScope,
)
@@ -88,7 +116,7 @@
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 0000000..f1e6a05
--- /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 0000000..b1c994c
--- /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 126e0e8..7caa5ccc 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 @@
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 ab441e3..6209f73 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.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.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 @@
@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 @@
@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 @@
@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 @@
keyguardBypassController =
KeyguardBypassController(
context,
+ testScope.backgroundScope,
tunerService,
statusBarStateController,
lockscreenUserManager,
keyguardStateController,
- shadeExpansionStateManager,
+ shadeRepository,
devicePostureController,
dumpManager
)
@@ -267,4 +280,25 @@
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 c0d248e..6484389 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.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 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 @@
mViewModel =
new KeyguardStatusBarViewModel(
mTestScope.getBackgroundScope(),
- mKeyguardInteractor);
+ mKeyguardInteractor,
+ new KeyguardStatusBarInteractor(new FakeKeyguardStatusBarRepository()),
+ mBatteryController);
allowTestableLooperAsMainThread();
TestableLooper.get(this).runWithLooper(() -> {
@@ -320,6 +322,15 @@
}
@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 f4078d5..1bc346d 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.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 @@
) {
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 @@
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 773a0d8..0209030 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 @@
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 0000000..758fe93a
--- /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 424218c..409ba48 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 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 cd009df..d6632a3 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.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 @@
// 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 1a893f8..bf77b1a 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.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.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 0000000..201926d
--- /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 6777734..766f748 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.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.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 @@
fun bouncerViewModel(
bouncerInteractor: BouncerInteractor,
authenticationInteractor: AuthenticationInteractor,
+ users: List<UserViewModel> = createUsers(),
): BouncerViewModel {
return BouncerViewModel(
applicationContext = context,
@@ -217,6 +226,13 @@
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 @@
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 7c70846..992ac62 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 @@
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 cad8fcf..c7b53c5 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 @@
@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 @@
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 @@
}
}
+ /**
+ * 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 4e5b058..0220dec 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 @@
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 @@
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 d479dfb..602855d 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 @@
* @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 @@
}
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 0a8fe62..b2716ec 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 @@
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 @@
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 @@
mCallback = callback;
mFullScreen = isFullScreen(context);
mContext = new ContextThemeWrapper(context, mThemeId);
+ mMaxInputLengthForAutofill = maxInputLengthForAutofill;
final LayoutInflater inflater = LayoutInflater.from(mContext);
@@ -432,10 +436,11 @@
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 24eab0b..c734680 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 @@
}
/**
+ * 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 @@
@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 @@
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 @@
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 @@
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 9dd0dca..852e36d 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 @@
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 20de40e..3d610d3 100644
--- a/services/core/java/com/android/server/ExplicitHealthCheckController.java
+++ b/services/core/java/com/android/server/ExplicitHealthCheckController.java
@@ -343,7 +343,7 @@
};
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 c094c12..dd54334 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -522,7 +522,8 @@
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 @@
}
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 6e984bb..b05b397 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -330,7 +330,7 @@
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 0615ecf..a97675f 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 @@
// 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 a680f50..cd3d2f0 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 @@
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 16b23ca..138186b 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 @@
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_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.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 @@
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 @@
*/
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 @@
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 @@
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 @@
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 @@
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 @@
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 c83a969..431aabd 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 @@
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 @@
*
* @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 @@
/**
* 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 @@
* {@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 f865e60..7d090db 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 @@
* 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 83a3125..c9528d8 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 @@
}
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 b8c2b86..1f12c88 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 @@
@NonNull
private final Object mLock = new Object();
- final IActivityManager mActivityManager = ActivityManager.getService();
+ IActivityManager mActivityManager;
@NonNull
@GuardedBy("mLock")
@@ -149,6 +149,9 @@
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 237bc92..a450b4d 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.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 @@
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 @@
}
}
- 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 @@
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 @@
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 901975b..9fd4720 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 @@
}
// 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 ca42400..f6fe9b1 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 @@
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 f0757db..5f4a1c5 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 @@
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 ee05e35..b738c1c 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 @@
// 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 ea5c9c2..5533759 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 @@
}
}
// 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 @@
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 c92a781..7109137 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 @@
// 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 3db7765..82452cc 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 @@
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 @@
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 c7a71ee..ce28682 100644
--- a/services/tests/PackageManagerServiceTests/host/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/Android.bp
@@ -34,8 +34,11 @@
],
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 @@
":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 0000000..c490604
--- /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 5cc3371..bee7c40 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 @@
"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 0376376..184c976 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 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 @@
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 @@
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 @@
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 3fc0e4f..255cb64 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 @@
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 6165260..d64cbcd 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 72c3ebe..e7ebd7db 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.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.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 @@
// 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 @@
}
@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 75716b9..241f7d8 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 @@
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 1ddb5c0..18c960e 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 @@
@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 0000000..f4845a5
--- /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);
+ }
+ }
+}