Merge "[CrashRecovery] Add module lib to Android SDK" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index c43aa75..f5bf437 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -13,71 +13,143 @@
// limitations under the License.
aconfig_srcjars = [
- ":android.app.usage.flags-aconfig-java{.generated_srcjars}",
+ // !!! KEEP THIS LIST ALPHABETICAL !!!
+ ":aconfig_mediacodec_flags_java_lib{.generated_srcjars}",
+ ":android.adaptiveauth.flags-aconfig-java{.generated_srcjars}",
+ ":android.app.flags-aconfig-java{.generated_srcjars}",
":android.app.smartspace.flags-aconfig-java{.generated_srcjars}",
+ ":android.app.usage.flags-aconfig-java{.generated_srcjars}",
+ ":android.appwidget.flags-aconfig-java{.generated_srcjars}",
+ ":android.chre.flags-aconfig-java{.generated_srcjars}",
":android.companion.flags-aconfig-java{.generated_srcjars}",
+ ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}",
+ ":android.content.flags-aconfig-java{.generated_srcjars}",
":android.content.pm.flags-aconfig-java{.generated_srcjars}",
":android.content.res.flags-aconfig-java{.generated_srcjars}",
+ ":android.credentials.flags-aconfig-java{.generated_srcjars}",
+ ":android.database.sqlite-aconfig-java{.generated_srcjars}",
+ ":android.hardware.biometrics.flags-aconfig-java{.generated_srcjars}",
":android.hardware.flags-aconfig-java{.generated_srcjars}",
":android.hardware.radio.flags-aconfig-java{.generated_srcjars}",
+ ":android.hardware.usb.flags-aconfig-java{.generated_srcjars}",
":android.location.flags-aconfig-java{.generated_srcjars}",
+ ":android.media.tv.flags-aconfig-java{.generated_srcjars}",
+ ":android.multiuser.flags-aconfig-java{.generated_srcjars}",
":android.net.vcn.flags-aconfig-java{.generated_srcjars}",
":android.nfc.flags-aconfig-java{.generated_srcjars}",
":android.os.flags-aconfig-java{.generated_srcjars}",
":android.os.vibrator.flags-aconfig-java{.generated_srcjars}",
+ ":android.permission.flags-aconfig-java{.generated_srcjars}",
+ ":android.provider.flags-aconfig-java{.generated_srcjars}",
":android.security.flags-aconfig-java{.generated_srcjars}",
":android.server.app.flags-aconfig-java{.generated_srcjars}",
+ ":android.service.autofill.flags-aconfig-java{.generated_srcjars}",
":android.service.chooser.flags-aconfig-java{.generated_srcjars}",
+ ":android.service.controls.flags-aconfig-java{.generated_srcjars}",
":android.service.dreams.flags-aconfig-java{.generated_srcjars}",
":android.service.notification.flags-aconfig-java{.generated_srcjars}",
- ":android.view.flags-aconfig-java{.generated_srcjars}",
+ ":android.service.voice.flags-aconfig-java{.generated_srcjars}",
+ ":android.speech.flags-aconfig-java{.generated_srcjars}",
+ ":android.tracing.flags-aconfig-java{.generated_srcjars}",
":android.view.accessibility.flags-aconfig-java{.generated_srcjars}",
+ ":android.view.contentcapture.flags-aconfig-java{.generated_srcjars}",
+ ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}",
+ ":android.view.flags-aconfig-java{.generated_srcjars}",
+ ":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}",
+ ":android.webkit.flags-aconfig-java{.generated_srcjars}",
+ ":android.widget.flags-aconfig-java{.generated_srcjars}",
":audio-framework-aconfig",
":camera_platform_flags_core_java_lib{.generated_srcjars}",
- ":com.android.window.flags.window-aconfig-java{.generated_srcjars}",
- ":android.hardware.biometrics.flags-aconfig-java{.generated_srcjars}",
":com.android.hardware.input-aconfig-java{.generated_srcjars}",
":com.android.input.flags-aconfig-java{.generated_srcjars}",
+ ":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}",
+ ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
+ ":com.android.net.flags-aconfig-java{.generated_srcjars}",
+ ":com.android.server.flags.services-aconfig-java{.generated_srcjars}",
":com.android.text.flags-aconfig-java{.generated_srcjars}",
+ ":com.android.window.flags.window-aconfig-java{.generated_srcjars}",
+ ":device_policy_aconfig_flags_lib{.generated_srcjars}",
+ ":display_flags_lib{.generated_srcjars}",
":framework-jobscheduler-job.flags-aconfig-java{.generated_srcjars}",
+ ":framework_graphics_flags_java_lib{.generated_srcjars}",
+ ":hwui_flags_java_lib{.generated_srcjars}",
+ ":power_flags_lib{.generated_srcjars}",
+ ":sdk_sandbox_flags_lib{.generated_srcjars}",
+ ":surfaceflinger_flags_java_lib{.generated_srcjars}",
":telecom_flags_core_java_lib{.generated_srcjars}",
":telephony_flags_core_java_lib{.generated_srcjars}",
- ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}",
- ":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}",
- ":android.widget.flags-aconfig-java{.generated_srcjars}",
- ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
- ":sdk_sandbox_flags_lib{.generated_srcjars}",
- ":android.permission.flags-aconfig-java{.generated_srcjars}",
- ":android.database.sqlite-aconfig-java{.generated_srcjars}",
- ":hwui_flags_java_lib{.generated_srcjars}",
- ":framework_graphics_flags_java_lib{.generated_srcjars}",
- ":display_flags_lib{.generated_srcjars}",
- ":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}",
- ":android.multiuser.flags-aconfig-java{.generated_srcjars}",
- ":android.app.flags-aconfig-java{.generated_srcjars}",
- ":android.credentials.flags-aconfig-java{.generated_srcjars}",
- ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}",
- ":com.android.server.flags.services-aconfig-java{.generated_srcjars}",
- ":android.service.controls.flags-aconfig-java{.generated_srcjars}",
- ":android.service.voice.flags-aconfig-java{.generated_srcjars}",
- ":android.media.tv.flags-aconfig-java{.generated_srcjars}",
- ":android.service.autofill.flags-aconfig-java{.generated_srcjars}",
- ":com.android.net.flags-aconfig-java{.generated_srcjars}",
- ":device_policy_aconfig_flags_lib{.generated_srcjars}",
- ":surfaceflinger_flags_java_lib{.generated_srcjars}",
- ":android.view.contentcapture.flags-aconfig-java{.generated_srcjars}",
- ":android.hardware.usb.flags-aconfig-java{.generated_srcjars}",
- ":android.tracing.flags-aconfig-java{.generated_srcjars}",
- ":android.appwidget.flags-aconfig-java{.generated_srcjars}",
- ":android.webkit.flags-aconfig-java{.generated_srcjars}",
- ":android.provider.flags-aconfig-java{.generated_srcjars}",
- ":android.chre.flags-aconfig-java{.generated_srcjars}",
- ":android.speech.flags-aconfig-java{.generated_srcjars}",
- ":power_flags_lib{.generated_srcjars}",
- ":android.content.flags-aconfig-java{.generated_srcjars}",
- ":aconfig_mediacodec_flags_java_lib{.generated_srcjars}",
+ // !!! KEEP THIS LIST ALPHABETICAL !!!
]
+stubs_defaults {
+ name: "framework-minus-apex-aconfig-declarations",
+ aconfig_declarations: [
+ "android.app.flags-aconfig",
+ "android.app.smartspace.flags-aconfig",
+ "android.app.usage.flags-aconfig",
+ "android.appwidget.flags-aconfig",
+ "android.companion.flags-aconfig",
+ "android.companion.virtual.flags-aconfig",
+ "android.content.pm.flags-aconfig",
+ "android.content.res.flags-aconfig",
+ "android.credentials.flags-aconfig",
+ "android.database.sqlite-aconfig",
+ "android.hardware.biometrics.flags-aconfig",
+ "android.hardware.flags-aconfig",
+ "android.hardware.radio.flags-aconfig",
+ "android.hardware.usb.flags-aconfig",
+ "android.location.flags-aconfig",
+ "android.media.audio-aconfig",
+ "android.media.audiopolicy-aconfig",
+ "android.media.midi-aconfig",
+ "android.media.tv.flags-aconfig",
+ "android.multiuser.flags-aconfig",
+ "android.net.vcn.flags-aconfig",
+ "android.nfc.flags-aconfig",
+ "android.os.flags-aconfig",
+ "android.os.vibrator.flags-aconfig",
+ "android.permission.flags-aconfig",
+ "android.provider.flags-aconfig",
+ "android.security.flags-aconfig",
+ "android.server.app.flags-aconfig",
+ "android.service.autofill.flags-aconfig",
+ "android.service.chooser.flags-aconfig",
+ "android.service.controls.flags-aconfig",
+ "android.service.dreams.flags-aconfig",
+ "android.service.notification.flags-aconfig",
+ "android.service.voice.flags-aconfig",
+ "android.speech.flags-aconfig",
+ "android.tracing.flags-aconfig",
+ "android.view.accessibility.flags-aconfig",
+ "android.view.contentcapture.flags-aconfig",
+ "android.view.contentprotection.flags-aconfig",
+ "android.view.flags-aconfig",
+ "android.view.inputmethod.flags-aconfig",
+ "android.webkit.flags-aconfig",
+ "android.widget.flags-aconfig",
+ "camera_platform_flags",
+ "chre_flags",
+ "com.android.hardware.input.input-aconfig",
+ "com.android.input.flags-aconfig",
+ "com.android.media.flags.bettertogether-aconfig",
+ "com.android.net.flags-aconfig",
+ "com.android.server.flags.services-aconfig",
+ "com.android.text.flags-aconfig",
+ "com.android.window.flags.window-aconfig",
+ "device_policy_aconfig_flags",
+ "display_flags",
+ "fold_lock_setting_flags",
+ "framework-jobscheduler-job.flags-aconfig",
+ "framework_graphics_flags",
+ "hwui_flags",
+ "power_flags",
+ "sdk_sandbox_flags",
+ "surfaceflinger_flags",
+ "telecom_flags",
+ "telephony_flags",
+ ],
+}
+
filegroup {
name: "framework-minus-apex-aconfig-srcjars",
srcs: aconfig_srcjars,
@@ -962,3 +1034,16 @@
aconfig_declarations: "android.content.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// Adaptive Auth
+aconfig_declarations {
+ name: "android.adaptiveauth.flags-aconfig",
+ package: "android.adaptiveauth",
+ srcs: ["core/java/android/adaptiveauth/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.adaptiveauth.flags-aconfig-java",
+ aconfig_declarations: "android.adaptiveauth.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/Ravenwood.bp b/Ravenwood.bp
index d13c4d7..0877bce 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -97,6 +97,7 @@
"framework-minus-apex.ravenwood",
"hoststubgen-helper-runtime.ravenwood",
"hoststubgen-helper-framework-runtime.ravenwood",
+ "core-libart-for-host",
"all-updatable-modules-system-stubs",
"junit",
"truth",
diff --git a/TEST_MAPPING b/TEST_MAPPING
index d59775f..c904eb4 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -138,15 +138,13 @@
}
],
"postsubmit-ravenwood": [
- // TODO(ravenwood) promote it to presubmit
- // TODO: Enable it once the infra knows how to run it.
-// {
-// "name": "CtsUtilTestCasesRavenwood",
-// "file_patterns": [
-// "*Ravenwood*",
-// "*ravenwood*"
-// ]
-// }
+ {
+ "name": "CtsUtilTestCasesRavenwood",
+ "host": true,
+ "file_patterns": [
+ "[Rr]avenwood"
+ ]
+ }
],
"postsubmit-managedprofile-stress": [
{
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 2c9af67..44afbe6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -951,7 +951,7 @@
@VisibleForTesting
static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;
@VisibleForTesting
- static final long DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS = 72 * HOUR_IN_MILLIS;
+ static final long DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS = 24 * HOUR_IN_MILLIS;
private static final long DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = MINUTE_IN_MILLIS;
@VisibleForTesting
final int[] DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS = {50, 60, 70, 80};
diff --git a/api/Android.bp b/api/Android.bp
index a5c4c78..b3b18b6 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -301,6 +301,7 @@
// classpath (or sources) somehow.
stubs_defaults {
name: "android-non-updatable-stubs-defaults",
+ defaults: ["framework-minus-apex-aconfig-declarations"],
srcs: [":android-non-updatable-stub-sources"],
sdk_version: "none",
system_modules: "none",
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
index 16bb896..55ea15d 100644
--- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -468,9 +468,9 @@
entry.name().c_str());
const auto& res_value = entry.res_value();
result.pairs.emplace_back(OverlayData::Value{
- name, TargetValueWithConfig{.config = entry.configuration(), .value = TargetValue{
+ name, TargetValueWithConfig{.value = TargetValue{
.data_type = static_cast<uint8_t>(res_value.data_type()),
- .data_value = res_value.data_value()}}});
+ .data_value = res_value.data_value()}, .config = entry.configuration()}});
}
}
}
diff --git a/cmds/idmap2/libidmap2/ResourceContainer.cpp b/cmds/idmap2/libidmap2/ResourceContainer.cpp
index 7869fbd..3c0e118 100644
--- a/cmds/idmap2/libidmap2/ResourceContainer.cpp
+++ b/cmds/idmap2/libidmap2/ResourceContainer.cpp
@@ -227,9 +227,9 @@
} else {
overlay_data.pairs.emplace_back(
OverlayData::Value{*target_resource, TargetValueWithConfig{
- .config = std::string(),
.value = TargetValue{.data_type = overlay_resource->dataType,
- .data_value = overlay_resource->data}}});
+ .data_value = overlay_resource->data},
+ .config = std::string()}});
}
}
@@ -268,10 +268,11 @@
std::unique_ptr<AssetManager2> am;
ZipAssetsProvider* zip_assets;
- static Result<ResState> Initialize(std::unique_ptr<ZipAssetsProvider> zip) {
+ static Result<ResState> Initialize(std::unique_ptr<ZipAssetsProvider> zip,
+ package_property_t flags) {
ResState state;
state.zip_assets = zip.get();
- if ((state.apk_assets = ApkAssets::Load(std::move(zip))) == nullptr) {
+ if ((state.apk_assets = ApkAssets::Load(std::move(zip), flags)) == nullptr) {
return Error("failed to load apk asset");
}
@@ -284,7 +285,7 @@
}
state.am = std::make_unique<AssetManager2>();
- if (!state.am->SetApkAssets({state.apk_assets})) {
+ if (!state.am->SetApkAssets({state.apk_assets}, false)) {
return Error("failed to create asset manager");
}
@@ -343,8 +344,8 @@
return state;
}
- auto state =
- ResState::Initialize(std::move(std::get<std::unique_ptr<ZipAssetsProvider>>(state_)));
+ auto state = ResState::Initialize(std::move(std::get<std::unique_ptr<ZipAssetsProvider>>(state_)),
+ PROPERTY_OPTIMIZE_NAME_LOOKUPS);
if (!state) {
return state.GetError();
}
diff --git a/core/api/current.txt b/core/api/current.txt
index ce7c0440..d4cec2d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -13675,11 +13675,8 @@
@FlaggedApi("android.content.res.font_scale_converter_public") public interface FontScaleConverter {
method public float convertDpToSp(float);
method public float convertSpToDp(float);
- }
-
- @FlaggedApi("android.content.res.font_scale_converter_public") public class FontScaleConverterFactory {
- method @FlaggedApi("android.content.res.font_scale_converter_public") @AnyThread @Nullable public static android.content.res.FontScaleConverter forScale(float);
- method @FlaggedApi("android.content.res.font_scale_converter_public") @AnyThread public static boolean isNonLinearFontScalingActive(float);
+ method @AnyThread @Nullable public static android.content.res.FontScaleConverter forScale(float);
+ method @AnyThread public static boolean isNonLinearFontScalingActive(float);
}
public class ObbInfo implements android.os.Parcelable {
@@ -16399,6 +16396,8 @@
field public static final int START_HYPHEN_EDIT_NO_EDIT = 0; // 0x0
field public static final int STRIKE_THRU_TEXT_FLAG = 16; // 0x10
field public static final int SUBPIXEL_TEXT_FLAG = 128; // 0x80
+ field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_LEFT_EDGE = 8192; // 0x2000
+ field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 16384; // 0x4000
field public static final int UNDERLINE_TEXT_FLAG = 8; // 0x8
}
@@ -18682,6 +18681,8 @@
method @Nullable public int getAllowedAuthenticators();
method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable public android.hardware.biometrics.PromptContentView getContentView();
method @Nullable public CharSequence getDescription();
+ method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.graphics.Bitmap getLogoBitmap();
+ method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @DrawableRes @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public int getLogoRes();
method @Nullable public CharSequence getNegativeButtonText();
method @Nullable public CharSequence getSubtitle();
method @NonNull public CharSequence getTitle();
@@ -18731,6 +18732,8 @@
method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setContentView(@NonNull android.hardware.biometrics.PromptContentView);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence);
method @Deprecated @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDeviceCredentialAllowed(boolean);
+ method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.hardware.biometrics.BiometricPrompt.Builder setLogo(@DrawableRes int);
+ method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.hardware.biometrics.BiometricPrompt.Builder setLogo(@NonNull android.graphics.Bitmap);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setTitle(@NonNull CharSequence);
@@ -31850,6 +31853,7 @@
method public long computeChargeTimeRemaining();
method public int getIntProperty(int);
method public long getLongProperty(int);
+ method @FlaggedApi("android.os.battery_part_status_api") @Nullable public String getStringProperty(int);
method public boolean isCharging();
field public static final String ACTION_CHARGING = "android.os.action.CHARGING";
field public static final String ACTION_DISCHARGING = "android.os.action.DISCHARGING";
@@ -43077,6 +43081,8 @@
field public static final String KEY_RTT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_VT_CALL_BOOL = "rtt_upgrade_supported_for_downgraded_vt_call";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL = "satellite_attach_supported_bool";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT = "satellite_connection_hysteresis_sec_int";
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT = "satellite_entitlement_status_refresh_days_int";
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL = "satellite_entitlement_supported_bool";
field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool";
field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool";
field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5296712..976fdc4 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -133,6 +133,7 @@
field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
field public static final String GET_APP_METADATA = "android.permission.GET_APP_METADATA";
field public static final String GET_APP_OPS_STATS = "android.permission.GET_APP_OPS_STATS";
+ field @FlaggedApi("android.app.bic_client") public static final String GET_BACKGROUND_INSTALLED_PACKAGES = "android.permission.GET_BACKGROUND_INSTALLED_PACKAGES";
field @FlaggedApi("android.app.get_binding_uid_importance") public static final String GET_BINDING_UID_IMPORTANCE = "android.permission.GET_BINDING_UID_IMPORTANCE";
field public static final String GET_HISTORICAL_APP_OPS_STATS = "android.permission.GET_HISTORICAL_APP_OPS_STATS";
field public static final String GET_PROCESS_STATE_AND_OOM_SCORE = "android.permission.GET_PROCESS_STATE_AND_OOM_SCORE";
@@ -294,6 +295,7 @@
field public static final String READ_RUNTIME_PROFILES = "android.permission.READ_RUNTIME_PROFILES";
field public static final String READ_SAFETY_CENTER_STATUS = "android.permission.READ_SAFETY_CENTER_STATUS";
field public static final String READ_SEARCH_INDEXABLES = "android.permission.READ_SEARCH_INDEXABLES";
+ field @FlaggedApi("android.app.system_terms_of_address_enabled") public static final String READ_SYSTEM_GRAMMATICAL_GENDER = "android.permission.READ_SYSTEM_GRAMMATICAL_GENDER";
field public static final String READ_SYSTEM_UPDATE_INFO = "android.permission.READ_SYSTEM_UPDATE_INFO";
field public static final String READ_WALLPAPER_INTERNAL = "android.permission.READ_WALLPAPER_INTERNAL";
field public static final String READ_WIFI_CREDENTIAL = "android.permission.READ_WIFI_CREDENTIAL";
@@ -10043,12 +10045,17 @@
field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_CHARGING_POLICY = 9; // 0x9
field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_FIRST_USAGE_DATE = 8; // 0x8
field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_MANUFACTURING_DATE = 7; // 0x7
+ field @FlaggedApi("android.os.battery_part_status_api") @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_PART_STATUS = 12; // 0xc
+ field @FlaggedApi("android.os.battery_part_status_api") @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_SERIAL_NUMBER = 11; // 0xb
field public static final int CHARGING_POLICY_ADAPTIVE_AC = 3; // 0x3
field public static final int CHARGING_POLICY_ADAPTIVE_AON = 2; // 0x2
field public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE = 4; // 0x4
field public static final int CHARGING_POLICY_DEFAULT = 1; // 0x1
field public static final String EXTRA_EVENTS = "android.os.extra.EVENTS";
field public static final String EXTRA_EVENT_TIMESTAMP = "android.os.extra.EVENT_TIMESTAMP";
+ field @FlaggedApi("android.os.battery_part_status_api") public static final int PART_STATUS_ORIGINAL = 1; // 0x1
+ field @FlaggedApi("android.os.battery_part_status_api") public static final int PART_STATUS_REPLACED = 2; // 0x2
+ field @FlaggedApi("android.os.battery_part_status_api") public static final int PART_STATUS_UNSUPPORTED = 0; // 0x0
}
public final class BatterySaverPolicyConfig implements android.os.Parcelable {
@@ -17164,6 +17171,7 @@
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; // 0x4
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT = 2; // 0x2
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; // 0x1
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 0d1d8d7..a5c2af7 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -483,10 +483,15 @@
}
public class UiModeManager {
+ method @FlaggedApi("android.app.modes_api") @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) public int getAttentionModeThemeOverlay();
method public boolean isNightModeLocked();
method public boolean isUiModeLocked();
method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean releaseProjection(int);
method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean requestProjection(int);
+ field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_DAY = 1002; // 0x3ea
+ field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_NIGHT = 1001; // 0x3e9
+ field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_OFF = 1000; // 0x3e8
+ field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_UNKNOWN = -1; // 0xffffffff
field public static final int PROJECTION_TYPE_ALL = -1; // 0xffffffff
field public static final int PROJECTION_TYPE_AUTOMOTIVE = 1; // 0x1
field public static final int PROJECTION_TYPE_NONE = 0; // 0x0
diff --git a/core/java/android/adaptiveauth/OWNERS b/core/java/android/adaptiveauth/OWNERS
new file mode 100644
index 0000000..0218a78
--- /dev/null
+++ b/core/java/android/adaptiveauth/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/adaptiveauth/OWNERS
\ No newline at end of file
diff --git a/core/java/android/adaptiveauth/flags.aconfig b/core/java/android/adaptiveauth/flags.aconfig
new file mode 100644
index 0000000..39e46bb
--- /dev/null
+++ b/core/java/android/adaptiveauth/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.adaptiveauth"
+
+flag {
+ name: "report_biometric_auth_attempts"
+ namespace: "biometrics"
+ description: "Control the usage of the biometric auth signal in adaptive auth"
+ bug: "285053096"
+}
\ No newline at end of file
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 669baf9..4b2e93f 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1540,9 +1540,16 @@
public static final int OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER =
AppProtoEnums.APP_OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER;
+ /**
+ * See {@link #OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER}.
+ * @hide
+ */
+ public static final int OP_READ_SYSTEM_GRAMMATICAL_GENDER =
+ AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 143;
+ public static final int _NUM_OP = 144;
/**
* All app ops represented as strings.
@@ -2375,6 +2382,14 @@
public static final String OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER =
"android:rapid_clear_notifications_by_listener";
+ /**
+ * Allows an application to read the system grammatical gender.
+ *
+ * @hide
+ */
+ public static final String OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER =
+ "android:read_system_grammatical_gender";
+
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
/** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2486,6 +2501,7 @@
OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
OP_MEDIA_ROUTING_CONTROL,
+ OP_READ_SYSTEM_GRAMMATICAL_GENDER,
};
static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2938,6 +2954,10 @@
OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER,
"RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER")
.setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_READ_SYSTEM_GRAMMATICAL_GENDER,
+ OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER, "READ_SYSTEM_GRAMMATICAL_GENDER")
+ .setPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER)
+ .build(),
};
// The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index 60b34cd..3b83024 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -95,6 +95,34 @@
int getNightModeCustomType();
/**
+ * Overlays current Night Mode value.
+ * {@code attentionModeThemeOverlayType}.
+ *
+ * @param attentionModeThemeOverlayType
+ * @hide
+ */
+ @EnforcePermission("MODIFY_DAY_NIGHT_MODE")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)")
+ void setAttentionModeThemeOverlay(int attentionModeThemeOverlayType);
+
+
+ /**
+ * Returns current Attention Mode overlay type.
+ * <p>
+ * returns
+ * <ul>
+ * <li>{@link #MODE_ATTENTION_OFF}</li>
+ * <li>{@link #MODE_ATTENTION_NIGHT}</li>
+ * <li>{@link #MODE_ATTENTION_DAY}</li>
+ * </ul>
+ * </p>
+ * @hide
+ */
+ @EnforcePermission("MODIFY_DAY_NIGHT_MODE")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)")
+ int getAttentionModeThemeOverlay();
+
+ /**
* Sets the dark mode for the given application. This setting is persisted and will override the
* system configuration for this application.
* 1 - notnight mode
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 476232c..ed0cfbe 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5652,7 +5652,7 @@
pillColor = Colors.flattenAlpha(
getColors(p).getTertiaryFixedDimAccentColor(), bgColor);
textColor = Colors.flattenAlpha(
- getColors(p).getOnTertiaryAccentTextColor(), pillColor);
+ getColors(p).getOnTertiaryFixedAccentTextColor(), pillColor);
}
contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor);
contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor);
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 0ccb9cd..a271328 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -17,6 +17,7 @@
package android.app;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.IntRange;
@@ -265,6 +266,60 @@
*/
public static final int MODE_NIGHT_YES = 2;
+ /** @hide */
+ @IntDef(prefix = { "MODE_ATTENTION_THEME_OVERLAY_" }, value = {
+ MODE_ATTENTION_THEME_OVERLAY_OFF,
+ MODE_ATTENTION_THEME_OVERLAY_NIGHT,
+ MODE_ATTENTION_THEME_OVERLAY_DAY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AttentionModeThemeOverlayType {}
+
+ /** @hide */
+ @IntDef(prefix = { "MODE_ATTENTION_THEME_OVERLAY_" }, value = {
+ MODE_ATTENTION_THEME_OVERLAY_OFF,
+ MODE_ATTENTION_THEME_OVERLAY_NIGHT,
+ MODE_ATTENTION_THEME_OVERLAY_DAY,
+ MODE_ATTENTION_THEME_OVERLAY_UNKNOWN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AttentionModeThemeOverlayReturnType {}
+
+ /**
+ * Constant for {@link #setAttentionModeThemeOverlay(int)} (int)} and {@link
+ * #getAttentionModeThemeOverlay()}: Keeps night mode as set by {@link #setNightMode(int)}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @TestApi
+ public static final int MODE_ATTENTION_THEME_OVERLAY_OFF = 1000;
+
+ /**
+ * Constant for {@link #setAttentionModeThemeOverlay(int)} (int)} and {@link
+ * #getAttentionModeThemeOverlay()}: Maintains night mode always on.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @TestApi
+ public static final int MODE_ATTENTION_THEME_OVERLAY_NIGHT = 1001;
+
+ /**
+ * Constant for {@link #setAttentionModeThemeOverlay(int)} (int)} and {@link
+ * #getAttentionModeThemeOverlay()}: Maintains night mode always off (Light).
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @TestApi
+ public static final int MODE_ATTENTION_THEME_OVERLAY_DAY = 1002;
+
+ /**
+ * Constant for {@link #getAttentionModeThemeOverlay()}: Error communication with server.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @TestApi
+ public static final int MODE_ATTENTION_THEME_OVERLAY_UNKNOWN = -1;
+
/**
* Granular types for {@link #setNightModeCustomType(int)}
* @hide
@@ -733,6 +788,55 @@
}
/**
+ * Overlays current Attention mode Night Mode overlay.
+ * {@code attentionModeThemeOverlayType}.
+ *
+ * @throws IllegalArgumentException if passed an unsupported type to
+ * {@code AttentionModeThemeOverlayType}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
+ public void setAttentionModeThemeOverlay(
+ @AttentionModeThemeOverlayType int attentionModeThemeOverlayType) {
+ if (sGlobals != null) {
+ try {
+ sGlobals.mService.setAttentionModeThemeOverlay(attentionModeThemeOverlayType);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Returns the currently configured Attention Mode theme overlay.
+ * <p>
+ * May be one of:
+ * <ul>
+ * <li>{@link #MODE_ATTENTION_THEME_OVERLAY_OFF}</li>
+ * <li>{@link #MODE_ATTENTION_THEME_OVERLAY_NIGHT}</li>
+ * <li>{@link #MODE_ATTENTION_THEME_OVERLAY_DAY}</li>
+ * <li>{@link #MODE_ATTENTION_THEME_OVERLAY_UNKNOWN}</li>
+ * </ul>
+ * </p>
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
+ public @AttentionModeThemeOverlayReturnType int getAttentionModeThemeOverlay() {
+ if (sGlobals != null) {
+ try {
+ return sGlobals.mService.getAttentionModeThemeOverlay();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return MODE_ATTENTION_THEME_OVERLAY_UNKNOWN;
+ }
+
+ /**
* Sets and persist the night mode for this application.
* <p>
* The mode can be one of:
diff --git a/core/java/android/content/res/FontScaleConverter.java b/core/java/android/content/res/FontScaleConverter.java
index 088949e..f4312a9 100644
--- a/core/java/android/content/res/FontScaleConverter.java
+++ b/core/java/android/content/res/FontScaleConverter.java
@@ -17,7 +17,9 @@
package android.content.res;
+import android.annotation.AnyThread;
import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
/**
* A converter for non-linear font scaling. Converts font sizes given in "sp" dimensions to a
@@ -40,4 +42,35 @@
* Converts a dimension in "dp" back to "sp".
*/
float convertDpToSp(float dp);
+
+ /**
+ * Returns true if non-linear font scaling curves would be in effect for the given scale, false
+ * if the scaling would follow a linear curve or for no scaling.
+ *
+ * <p>Example usage: {@code
+ * isNonLinearFontScalingActive(getResources().getConfiguration().fontScale)}
+ */
+ @AnyThread
+ static boolean isNonLinearFontScalingActive(float fontScale) {
+ return FontScaleConverterFactory.isNonLinearFontScalingActive(fontScale);
+ }
+
+ /**
+ * Finds a matching FontScaleConverter for the given fontScale factor.
+ *
+ * Generally you shouldn't need this; you can use {@link
+ * android.util.TypedValue#applyDimension(int, float, DisplayMetrics)} directly and it will do
+ * the scaling conversion for you. Dimens and resources loaded from XML will also be
+ * automatically converted. But for UI frameworks or other situations where you need to do the
+ * conversion without an Android Context, you can use this method.
+ *
+ * @param fontScale the scale factor, usually from {@link Configuration#fontScale}.
+ *
+ * @return a converter for the given scale, or null if non-linear scaling should not be used.
+ */
+ @Nullable
+ @AnyThread
+ static FontScaleConverter forScale(float fontScale) {
+ return FontScaleConverterFactory.forScale(fontScale);
+ }
}
diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java
index 5d31cc0..cbe4c62 100644
--- a/core/java/android/content/res/FontScaleConverterFactory.java
+++ b/core/java/android/content/res/FontScaleConverterFactory.java
@@ -17,7 +17,6 @@
package android.content.res;
import android.annotation.AnyThread;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.MathUtils;
@@ -32,8 +31,9 @@
* android.util.TypedValue#applyDimension(int, float, DisplayMetrics)} directly and it will do the
* scaling conversion for you. But for UI frameworks or other situations where you need to do the
* conversion without an Android Context, you can use this class.
+ *
+ * @hide
*/
-@FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
public class FontScaleConverterFactory {
private static final float SCALE_KEY_MULTIPLIER = 100f;
@@ -124,7 +124,6 @@
* <p>Example usage:
* <code>isNonLinearFontScalingActive(getResources().getConfiguration().fontScale)</code>
*/
- @FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
@AnyThread
public static boolean isNonLinearFontScalingActive(float fontScale) {
return fontScale >= sMinScaleBeforeCurvesApplied;
@@ -137,7 +136,6 @@
*
* @return a converter for the given scale, or null if non-linear scaling should not be used.
*/
- @FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
@Nullable
@AnyThread
public static FontScaleConverter forScale(float fontScale) {
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index a0f4d8d..c0424db 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -16,6 +16,7 @@
package android.hardware.biometrics;
+import static android.Manifest.permission.MANAGE_BIOMETRIC_DIALOG;
import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
@@ -25,6 +26,7 @@
import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
import android.annotation.CallbackExecutor;
+import android.annotation.DrawableRes;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -33,6 +35,7 @@
import android.annotation.TestApi;
import android.content.Context;
import android.content.DialogInterface;
+import android.graphics.Bitmap;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Binder;
@@ -160,6 +163,45 @@
}
/**
+ * Optional: Sets the drawable resource of the logo that will be shown on the prompt.
+ *
+ * <p> Note that using this method is not recommended in most scenarios because the calling
+ * application's icon will be used by default. Setting the logo is intended for large
+ * bundled applications that perform a wide range of functions and need to show distinct
+ * icons for each function.
+ *
+ * @param logoRes A drawable resource of the logo that will be shown on the prompt.
+ * @return This builder.
+ */
+ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+ @NonNull
+ public BiometricPrompt.Builder setLogo(@DrawableRes int logoRes) {
+ mPromptInfo.setLogoRes(logoRes);
+ return this;
+ }
+
+ /**
+ * Optional: Sets the bitmap drawable of the logo that will be shown on the prompt.
+ *
+ * <p> Note that using this method is not recommended in most scenarios because the calling
+ * application's icon will be used by default. Setting the logo is intended for large
+ * bundled applications that perform a wide range of functions and need to show distinct
+ * icons for each function.
+ *
+ * @param logoBitmap A bitmap drawable of the logo that will be shown on the prompt.
+ * @return This builder.
+ */
+ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+ @NonNull
+ public BiometricPrompt.Builder setLogo(@NonNull Bitmap logoBitmap) {
+ mPromptInfo.setLogoBitmap(logoBitmap);
+ return this;
+ }
+
+
+ /**
* Required: Sets the title that will be shown on the prompt.
* @param title The title to display.
* @return This builder.
@@ -676,6 +718,34 @@
}
/**
+ * Gets the drawable resource of the logo for the prompt, as set by
+ * {@link Builder#setLogo(int)}. Currently for system applications use only.
+ *
+ * @return The drawable resource of the logo, or -1 if the prompt has no logo resource set.
+ */
+ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+ @DrawableRes
+ public int getLogoRes() {
+ return mPromptInfo.getLogoRes();
+ }
+
+ /**
+ * Gets the logo bitmap for the prompt, as set by {@link Builder#setLogo(Bitmap)}. Currently for
+ * system applications use only.
+ *
+ * @return The logo bitmap of the prompt, or null if the prompt has no logo bitmap set.
+ */
+ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+ @Nullable
+ public Bitmap getLogoBitmap() {
+ return mPromptInfo.getLogoBitmap();
+ }
+
+
+
+ /**
* Gets the title for the prompt, as set by {@link Builder#setTitle(CharSequence)}.
* @return The title of the prompt, which is guaranteed to be non-null.
*/
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index c73ebd4..d788b37 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -16,8 +16,10 @@
package android.hardware.biometrics;
+import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.Bitmap;
import android.os.Parcel;
import android.os.Parcelable;
@@ -30,6 +32,8 @@
*/
public class PromptInfo implements Parcelable {
+ @DrawableRes private int mLogoRes = -1;
+ @Nullable private Bitmap mLogoBitmap;
@NonNull private CharSequence mTitle;
private boolean mUseDefaultTitle;
@Nullable private CharSequence mSubtitle;
@@ -56,6 +60,8 @@
}
PromptInfo(Parcel in) {
+ mLogoRes = in.readInt();
+ mLogoBitmap = in.readTypedObject(Bitmap.CREATOR);
mTitle = in.readCharSequence();
mUseDefaultTitle = in.readBoolean();
mSubtitle = in.readCharSequence();
@@ -98,6 +104,8 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mLogoRes);
+ dest.writeTypedObject(mLogoBitmap, 0);
dest.writeCharSequence(mTitle);
dest.writeBoolean(mUseDefaultTitle);
dest.writeCharSequence(mSubtitle);
@@ -156,9 +164,30 @@
}
return false;
}
+
+ /**
+ * Returns whether MANAGE_BIOMETRIC_DIALOG is contained.
+ */
+ public boolean containsManageBioApiConfigurations() {
+ if (mLogoRes != -1) {
+ return true;
+ } else if (mLogoBitmap != null) {
+ return true;
+ }
+ return false;
+ }
// LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/BiometricPrompt.java)
// Setters
+ public void setLogoRes(@DrawableRes int logoRes) {
+ mLogoRes = logoRes;
+ checkOnlyOneLogoSet();
+ }
+
+ public void setLogoBitmap(@NonNull Bitmap logoBitmap) {
+ mLogoBitmap = logoBitmap;
+ checkOnlyOneLogoSet();
+ }
public void setTitle(CharSequence title) {
mTitle = title;
@@ -244,6 +273,14 @@
}
// Getters
+ @DrawableRes
+ public int getLogoRes() {
+ return mLogoRes;
+ }
+
+ public Bitmap getLogoBitmap() {
+ return mLogoBitmap;
+ }
public CharSequence getTitle() {
return mTitle;
@@ -337,4 +374,11 @@
public boolean isShowEmergencyCallButton() {
return mShowEmergencyCallButton;
}
+
+ private void checkOnlyOneLogoSet() {
+ if (mLogoRes != -1 && mLogoBitmap != null) {
+ throw new IllegalStateException(
+ "Exclusively one of logo resource or logo bitmap can be set");
+ }
+ }
}
diff --git a/core/java/android/hardware/radio/TEST_MAPPING b/core/java/android/hardware/radio/TEST_MAPPING
new file mode 100644
index 0000000..ee4eeb6
--- /dev/null
+++ b/core/java/android/hardware/radio/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "imports": [
+ {
+ "path": "frameworks/base/core/tests/BroadcastRadioTests"
+ }
+ ]
+}
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 25fba60..b9bb059 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -17,9 +17,11 @@
package android.os;
import static android.os.Flags.FLAG_STATE_OF_HEALTH_PUBLIC;
+import static android.os.Flags.FLAG_BATTERY_PART_STATUS_API;
import android.Manifest.permission;
import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
@@ -236,6 +238,31 @@
public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE =
OsProtoEnums.CHARGING_POLICY_ADAPTIVE_LONGLIFE; // = 4
+ // values for "battery part status" property
+ /**
+ * Battery part status is not supported.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int PART_STATUS_UNSUPPORTED = 0;
+
+ /**
+ * Battery is the original device battery.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int PART_STATUS_ORIGINAL = 1;
+
+ /**
+ * Battery has been replaced.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int PART_STATUS_REPLACED = 2;
+
/** @hide */
@SuppressLint("UnflaggedApi") // TestApi without associated feature.
@TestApi
@@ -366,6 +393,32 @@
@FlaggedApi(FLAG_STATE_OF_HEALTH_PUBLIC)
public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10;
+ /**
+ * Battery part serial number.
+ *
+ * <p class="note">
+ * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission.
+ *
+ * @hide
+ */
+ @RequiresPermission(permission.BATTERY_STATS)
+ @SystemApi
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int BATTERY_PROPERTY_SERIAL_NUMBER = 11;
+
+ /**
+ * Battery part status from a BATTERY_PART_STATUS_* value.
+ *
+ * <p class="note">
+ * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission.
+ *
+ * @hide
+ */
+ @RequiresPermission(permission.BATTERY_STATS)
+ @SystemApi
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int BATTERY_PROPERTY_PART_STATUS = 12;
+
private final Context mContext;
private final IBatteryStats mBatteryStats;
private final IBatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
@@ -431,6 +484,25 @@
}
/**
+ * Same as queryProperty, but for strings.
+ */
+ private String queryStringProperty(int id) {
+ if (mBatteryPropertiesRegistrar == null) {
+ return null;
+ }
+
+ try {
+ BatteryProperty prop = new BatteryProperty();
+ if (mBatteryPropertiesRegistrar.getProperty(id, prop) == 0) {
+ return prop.getString();
+ }
+ return null;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Return the value of a battery property of integer type.
*
* @param id identifier of the requested property
@@ -464,6 +536,21 @@
}
/**
+ * Return the value of a battery property of String type. If the
+ * platform does not provide the property queried, this value will
+ * be null.
+ *
+ * @param id identifier of the requested property.
+ *
+ * @return the property value, or null if not supported.
+ */
+ @Nullable
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public String getStringProperty(int id) {
+ return queryStringProperty(id);
+ }
+
+ /**
* Return true if the plugType given is wired
* @param plugType {@link #BATTERY_PLUGGED_AC}, {@link #BATTERY_PLUGGED_USB},
* or {@link #BATTERY_PLUGGED_WIRELESS}
diff --git a/core/java/android/os/BatteryProperty.java b/core/java/android/os/BatteryProperty.java
index b40988a..464577f 100644
--- a/core/java/android/os/BatteryProperty.java
+++ b/core/java/android/os/BatteryProperty.java
@@ -28,12 +28,14 @@
*/
public class BatteryProperty implements Parcelable {
private long mValueLong;
+ private String mValueString;
/**
* @hide
*/
public BatteryProperty() {
mValueLong = Long.MIN_VALUE;
+ mValueString = null;
}
/**
@@ -46,14 +48,23 @@
/**
* @hide
*/
+ public String getString() {
+ return mValueString;
+ }
+
+ /**
+ * @hide
+ */
public void setLong(long val) {
mValueLong = val;
}
- /*
- * Parcel read/write code must be kept in sync with
- * frameworks/native/services/batteryservice/BatteryProperty.cpp
+ /**
+ * @hide
*/
+ public void setString(String val) {
+ mValueString = val;
+ }
private BatteryProperty(Parcel p) {
readFromParcel(p);
@@ -61,10 +72,12 @@
public void readFromParcel(Parcel p) {
mValueLong = p.readLong();
+ mValueString = p.readString8();
}
public void writeToParcel(Parcel p, int flags) {
p.writeLong(mValueLong);
+ p.writeString8(mValueString);
}
public static final @android.annotation.NonNull Parcelable.Creator<BatteryProperty> CREATOR
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 0db90bf..82518bf 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -106,3 +106,11 @@
bug: "305311707"
is_fixed_read_only: true
}
+
+flag {
+ name: "battery_part_status_api"
+ namespace: "phoenix"
+ description: "Feature flag for adding Health HAL v3 APIs."
+ is_fixed_read_only: true
+ bug: "309792384"
+}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 39b6aeb..db8f52c 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -86,3 +86,10 @@
description: "This flag is used to enabled the Wallet Role for all users on the device"
bug: "283989236"
}
+
+flag {
+ name: "runtime_permission_appops_mapping"
+ namespace: "permissions"
+ description: "Use runtime permission state to determine appop state"
+ bug: "266164193"
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7d84bb3..58159c2 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3176,15 +3176,7 @@
}
public void destroy() {
- try {
- // If this process is the system server process, mArray is the same object as
- // the memory int array kept inside SettingsProvider, so skipping the close()
- if (!Settings.isInSystemServer() && !mArray.isClosed()) {
- mArray.close();
- }
- } catch (IOException e) {
- Log.e(TAG, "Error closing backing array", e);
- }
+ maybeCloseGenerationArray(mArray);
}
@Override
@@ -3197,6 +3189,21 @@
}
}
+ private static void maybeCloseGenerationArray(@Nullable MemoryIntArray array) {
+ if (array == null) {
+ return;
+ }
+ try {
+ // If this process is the system server process, the MemoryIntArray received from Parcel
+ // is the same object as the one kept inside SettingsProvider, so skipping the close().
+ if (!Settings.isInSystemServer() && !array.isClosed()) {
+ array.close();
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error closing the generation tracking array", e);
+ }
+ }
+
private static final class ContentProviderHolder {
private final Object mLock = new Object();
@@ -3496,6 +3503,8 @@
mGenerationTrackers.put(name, new GenerationTracker(name,
array, index, generation,
mGenerationTrackerErrorHandler));
+ } else {
+ maybeCloseGenerationArray(array);
}
}
if (mGenerationTrackers.get(name) != null
@@ -3733,6 +3742,8 @@
new GenerationTracker(prefix, array, index, generation,
mGenerationTrackerErrorHandler));
currentGeneration = generation;
+ } else {
+ maybeCloseGenerationArray(array);
}
}
if (mGenerationTrackers.get(prefix) != null && currentGeneration
@@ -7310,6 +7321,28 @@
"bluetooth_le_broadcast_app_source_name";
/**
+ * This is used by LocalBluetoothLeBroadcast to downgrade the broadcast quality to improve
+ * compatibility.
+ *
+ * <ul>
+ * <li>0 = false
+ * <li>1 = true
+ * </ul>
+ *
+ * @hide
+ */
+ public static final String BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY =
+ "bluetooth_le_broadcast_improve_compatibility";
+
+ /**
+ * This is used by LocalBluetoothLeBroadcast to store the fallback active device address.
+ *
+ * @hide
+ */
+ public static final String BLUETOOTH_LE_BROADCAST_FALLBACK_ACTIVE_DEVICE_ADDRESS =
+ "bluetooth_le_broadcast_fallback_active_device_address";
+
+ /**
* Ringtone routing value for hearing aid. It routes ringtone to hearing aid or device
* speaker.
* <ul>
diff --git a/core/java/android/provider/TEST_MAPPING b/core/java/android/provider/TEST_MAPPING
index 8b4a99e..d5ac7a7 100644
--- a/core/java/android/provider/TEST_MAPPING
+++ b/core/java/android/provider/TEST_MAPPING
@@ -28,5 +28,10 @@
}
]
}
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsDeviceConfigTestCases"
+ }
]
}
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 30524a1..1994058 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -53,7 +53,7 @@
flag {
name: "frp_enforcement"
- namespace: "android_hw_security"
+ namespace: "hardware_backed_security"
description: "This flag controls whether PDB enforces FRP"
bug: "290312729"
is_fixed_read_only: true
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index ac5eb3c..b268c2e 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -671,9 +671,18 @@
if (mLtrWithoutBidi) {
// If the whole text is LTR direction, just apply whole region.
if (builder == null) {
- mWholeWidth += paint.getTextRunAdvances(
- mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */,
- mWidths.getRawArray(), start);
+ // For the compatibility reasons, the letter spacing should not be dropped at the
+ // left and right edge.
+ int oldFlag = paint.getFlags();
+ paint.setFlags(paint.getFlags()
+ | (Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE));
+ try {
+ mWholeWidth += paint.getTextRunAdvances(
+ mCopiedBuffer, start, end - start, start, end - start,
+ false /* isRtl */, mWidths.getRawArray(), start);
+ } finally {
+ paint.setFlags(oldFlag);
+ }
} else {
builder.appendStyleRun(paint, config, end - start, false /* isRtl */);
}
@@ -690,9 +699,16 @@
final boolean isRtl = (level & 0x1) != 0;
if (builder == null) {
final int levelLength = levelEnd - levelStart;
- mWholeWidth += paint.getTextRunAdvances(
- mCopiedBuffer, levelStart, levelLength, levelStart, levelLength,
- isRtl, mWidths.getRawArray(), levelStart);
+ int oldFlag = paint.getFlags();
+ paint.setFlags(paint.getFlags()
+ | (Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE));
+ try {
+ mWholeWidth += paint.getTextRunAdvances(
+ mCopiedBuffer, levelStart, levelLength, levelStart, levelLength,
+ isRtl, mWidths.getRawArray(), levelStart);
+ } finally {
+ paint.setFlags(oldFlag);
+ }
} else {
builder.appendStyleRun(paint, config, levelEnd - levelStart, isRtl);
}
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 135935c..2175b47 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -291,6 +291,97 @@
}
/**
+ * Returns the run flag of at the given BiDi run.
+ *
+ * @param bidiRunIndex a BiDi run index.
+ * @return a run flag of the given BiDi run.
+ */
+ @VisibleForTesting
+ public static int calculateRunFlag(int bidiRunIndex, int bidiRunCount, int lineDirection) {
+ if (bidiRunCount == 1) {
+ // Easy case. If there is only single run, it is most left and most right run.
+ return Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+ }
+ if (bidiRunIndex != 0 && bidiRunIndex != (bidiRunCount - 1)) {
+ // Easy case. If the given run is the middle of the line, it is not the most left or
+ // the most right run.
+ return 0;
+ }
+
+ int runFlag = 0;
+ // For the historical reasons, the BiDi implementation of Android works differently
+ // from the Java BiDi APIs. The mDirections holds the BiDi runs in visual order, but
+ // it is reversed order if the paragraph direction is RTL. So, the first BiDi run of
+ // mDirections is located the most left of the line if the paragraph direction is LTR.
+ // If the paragraph direction is RTL, the first BiDi run is located the most right of
+ // the line.
+ if (bidiRunIndex == 0) {
+ if (lineDirection == Layout.DIR_LEFT_TO_RIGHT) {
+ runFlag |= Paint.TEXT_RUN_FLAG_LEFT_EDGE;
+ } else {
+ runFlag |= Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+ }
+ }
+ if (bidiRunIndex == (bidiRunCount - 1)) {
+ if (lineDirection == Layout.DIR_LEFT_TO_RIGHT) {
+ runFlag |= Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+ } else {
+ runFlag |= Paint.TEXT_RUN_FLAG_LEFT_EDGE;
+ }
+ }
+ return runFlag;
+ }
+
+ /**
+ * Resolve the runFlag for the inline span range.
+ *
+ * @param runFlag the runFlag of the current BiDi run.
+ * @param isRtlRun true for RTL run, false for LTR run.
+ * @param runStart the inclusive BiDi run start offset.
+ * @param runEnd the exclusive BiDi run end offset.
+ * @param spanStart the inclusive span start offset.
+ * @param spanEnd the exclusive span end offset.
+ * @return the resolved runFlag.
+ */
+ @VisibleForTesting
+ public static int resolveRunFlagForSubSequence(int runFlag, boolean isRtlRun, int runStart,
+ int runEnd, int spanStart, int spanEnd) {
+ if (runFlag == 0) {
+ // Easy case. If the run is in the middle of the line, any inline span is also in the
+ // middle of the line.
+ return 0;
+ }
+ int localRunFlag = runFlag;
+ if ((runFlag & Paint.TEXT_RUN_FLAG_LEFT_EDGE) != 0) {
+ if (isRtlRun) {
+ if (spanEnd != runEnd) {
+ // In the RTL context, the last run is the most left run.
+ localRunFlag &= ~Paint.TEXT_RUN_FLAG_LEFT_EDGE;
+ }
+ } else { // LTR
+ if (spanStart != runStart) {
+ // In the LTR context, the first run is the most left run.
+ localRunFlag &= ~Paint.TEXT_RUN_FLAG_LEFT_EDGE;
+ }
+ }
+ }
+ if ((runFlag & Paint.TEXT_RUN_FLAG_RIGHT_EDGE) != 0) {
+ if (isRtlRun) {
+ if (spanStart != runStart) {
+ // In the RTL context, the start of the run is the most right run.
+ localRunFlag &= ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+ }
+ } else { // LTR
+ if (spanEnd != runEnd) {
+ // In the LTR context, the last run is the most right position.
+ localRunFlag &= ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+ }
+ }
+ }
+ return localRunFlag;
+ }
+
+ /**
* Renders the TextLine.
*
* @param c the canvas to render on
@@ -308,11 +399,13 @@
final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+ final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
+
int segStart = runStart;
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
if (j == runLimit || charAt(j) == TAB_CHAR) {
h += drawRun(c, segStart, j, runIsRtl, x + h, top, y, bottom,
- runIndex != (runCount - 1) || j != mLen);
+ runIndex != (runCount - 1) || j != mLen, runFlag);
if (j != runLimit) { // charAt(j) == TAB_CHAR
h = mDir * nextTab(h * mDir);
@@ -371,11 +464,12 @@
final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+ final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
int segStart = runStart;
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
if (j == runLimit || charAt(j) == TAB_CHAR) {
horizontal += shapeRun(consumer, segStart, j, runIsRtl, x + horizontal,
- runIndex != (runCount - 1) || j != mLen);
+ runIndex != (runCount - 1) || j != mLen, runFlag);
if (j != runLimit) { // charAt(j) == TAB_CHAR
horizontal = mDir * nextTab(horizontal * mDir);
@@ -441,11 +535,13 @@
}
float h = 0;
- for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+ final int runCount = mDirections.getRunCount();
+ for (int runIndex = 0; runIndex < runCount; runIndex++) {
final int runStart = mDirections.getRunStart(runIndex);
if (runStart > mLen) break;
final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+ final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
int segStart = runStart;
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
@@ -455,16 +551,16 @@
if (targetIsInThisSegment && sameDirection) {
return h + measureRun(segStart, offset, j, runIsRtl, fmi, drawBounds, null,
- 0, h, lineInfo);
+ 0, h, lineInfo, runFlag);
}
final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi, drawBounds,
- null, 0, h, lineInfo);
+ null, 0, h, lineInfo, runFlag);
h += sameDirection ? segmentWidth : -segmentWidth;
if (targetIsInThisSegment) {
return h + measureRun(segStart, offset, j, runIsRtl, null, null, null, 0,
- h, lineInfo);
+ h, lineInfo, runFlag);
}
if (j != runLimit) { // charAt(j) == TAB_CHAR
@@ -543,20 +639,21 @@
+ "result, needed: " + mLen + " had: " + advances.length);
}
float h = 0;
- for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+ final int runCount = mDirections.getRunCount();
+ for (int runIndex = 0; runIndex < runCount; runIndex++) {
final int runStart = mDirections.getRunStart(runIndex);
if (runStart > mLen) break;
final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+ final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
int segStart = runStart;
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
if (j == runLimit || charAt(j) == TAB_CHAR) {
final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
-
final float segmentWidth =
measureRun(segStart, j, j, runIsRtl, null, null, advances, segStart, 0,
- null);
+ null, runFlag);
final float oldh = h;
h += sameDirection ? segmentWidth : -segmentWidth;
@@ -608,11 +705,13 @@
}
float horizontal = 0;
- for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+ final int runCount = mDirections.getRunCount();
+ for (int runIndex = 0; runIndex < runCount; runIndex++) {
final int runStart = mDirections.getRunStart(runIndex);
if (runStart > mLen) break;
final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+ final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
int segStart = runStart;
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; ++j) {
@@ -629,7 +728,7 @@
final float previousSegEndHorizontal = measurement[segStart];
final float width =
measureRun(segStart, j, j, runIsRtl, fmi, null, measurement, segStart,
- 0, null);
+ 0, null, runFlag);
horizontal += sameDirection ? width : -width;
float currHorizontal = sameDirection ? oldHorizontal : horizontal;
@@ -686,22 +785,24 @@
* @param y the baseline
* @param bottom the bottom of the line
* @param needWidth true if the width value is required.
+ * @param runFlag the run flag to be applied for this run.
* @return the signed width of the run, based on the paragraph direction.
* Only valid if needWidth is true.
*/
private float drawRun(Canvas c, int start,
int limit, boolean runIsRtl, float x, int top, int y, int bottom,
- boolean needWidth) {
+ boolean needWidth, int runFlag) {
if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
- float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null);
+ float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null,
+ runFlag);
handleRun(start, limit, limit, runIsRtl, c, null, x + w, top,
- y, bottom, null, null, false, null, 0, null);
+ y, bottom, null, null, false, null, 0, null, runFlag);
return w;
}
return handleRun(start, limit, limit, runIsRtl, c, null, x, top,
- y, bottom, null, null, needWidth, null, 0, null);
+ y, bottom, null, null, needWidth, null, 0, null, runFlag);
}
/**
@@ -718,19 +819,21 @@
* @param advancesIndex the start index to fill in the advance information.
* @param x horizontal offset of the run.
* @param lineInfo an optional output parameter for filling line information.
+ * @param runFlag the run flag to be applied for this run.
* @return the signed width from the start of the run to the leading edge
* of the character at offset, based on the run (not paragraph) direction
*/
private float measureRun(int start, int offset, int limit, boolean runIsRtl,
@Nullable FontMetricsInt fmi, @Nullable RectF drawBounds, @Nullable float[] advances,
- int advancesIndex, float x, @Nullable LineInfo lineInfo) {
+ int advancesIndex, float x, @Nullable LineInfo lineInfo, int runFlag) {
if (drawBounds != null && (mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
- float w = -measureRun(start, offset, limit, runIsRtl, null, null, null, 0, 0, null);
+ float w = -measureRun(start, offset, limit, runIsRtl, null, null, null, 0, 0, null,
+ runFlag);
return handleRun(start, offset, limit, runIsRtl, null, null, x + w, 0, 0, 0, fmi,
- drawBounds, true, advances, advancesIndex, lineInfo);
+ drawBounds, true, advances, advancesIndex, lineInfo, runFlag);
}
return handleRun(start, offset, limit, runIsRtl, null, null, x, 0, 0, 0, fmi, drawBounds,
- true, advances, advancesIndex, lineInfo);
+ true, advances, advancesIndex, lineInfo, runFlag);
}
/**
@@ -742,21 +845,23 @@
* @param runIsRtl true if the run is right-to-left
* @param x the position of the run that is closest to the leading margin
* @param needWidth true if the width value is required.
+ * @param runFlag the run flag to be applied for this run.
* @return the signed width of the run, based on the paragraph direction.
* Only valid if needWidth is true.
*/
private float shapeRun(TextShaper.GlyphsConsumer consumer, int start,
- int limit, boolean runIsRtl, float x, boolean needWidth) {
+ int limit, boolean runIsRtl, float x, boolean needWidth, int runFlag) {
if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
- float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null);
+ float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null,
+ runFlag);
handleRun(start, limit, limit, runIsRtl, null, consumer, x + w, 0, 0, 0, null, null,
- false, null, 0, null);
+ false, null, 0, null, runFlag);
return w;
}
return handleRun(start, limit, limit, runIsRtl, null, consumer, x, 0, 0, 0, null, null,
- needWidth, null, 0, null);
+ needWidth, null, 0, null, runFlag);
}
@@ -1160,6 +1265,7 @@
* @param advances receives the advance information about the requested run, can be null.
* @param advancesIndex the start index to fill in the advance information.
* @param lineInfo an optional output parameter for filling line information.
+ * @param runFlag the run flag to be applied for this run.
* @return the signed width of the run based on the run direction; only
* valid if needWidth is true
*/
@@ -1168,8 +1274,8 @@
Canvas c, TextShaper.GlyphsConsumer consumer, float x, int top, int y, int bottom,
FontMetricsInt fmi, RectF drawBounds, boolean needWidth, int offset,
@Nullable ArrayList<DecorationInfo> decorations,
- @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo) {
-
+ @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo,
+ int runFlag) {
if (mIsJustifying) {
wp.setWordSpacing(mAddedWidthForJustify);
}
@@ -1187,7 +1293,16 @@
}
float totalWidth = 0;
-
+ if ((runFlag & Paint.TEXT_RUN_FLAG_LEFT_EDGE) == Paint.TEXT_RUN_FLAG_LEFT_EDGE) {
+ wp.setFlags(wp.getFlags() | Paint.TEXT_RUN_FLAG_LEFT_EDGE);
+ } else {
+ wp.setFlags(wp.getFlags() & ~Paint.TEXT_RUN_FLAG_LEFT_EDGE);
+ }
+ if ((runFlag & Paint.TEXT_RUN_FLAG_RIGHT_EDGE) == Paint.TEXT_RUN_FLAG_RIGHT_EDGE) {
+ wp.setFlags(wp.getFlags() | Paint.TEXT_RUN_FLAG_RIGHT_EDGE);
+ } else {
+ wp.setFlags(wp.getFlags() & ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE);
+ }
final int numDecorations = decorations == null ? 0 : decorations.size();
if (needWidth || ((c != null || consumer != null) && (wp.bgColor != 0
|| numDecorations != 0 || runIsRtl))) {
@@ -1419,6 +1534,7 @@
* @param advances receives the advance information about the requested run, can be null.
* @param advancesIndex the start index to fill in the advance information.
* @param lineInfo an optional output parameter for filling line information.
+ * @param runFlag the run flag to be applied for this run.
* @return the signed width of the run based on the run direction; only
* valid if needWidth is true
*/
@@ -1426,7 +1542,8 @@
int limit, boolean runIsRtl, Canvas c,
TextShaper.GlyphsConsumer consumer, float x, int top, int y,
int bottom, FontMetricsInt fmi, RectF drawBounds, boolean needWidth,
- @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo) {
+ @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo,
+ int runFlag) {
if (measureLimit < start || measureLimit > limit) {
throw new IndexOutOfBoundsException("measureLimit (" + measureLimit + ") is out of "
@@ -1473,7 +1590,7 @@
wp.setEndHyphenEdit(adjustEndHyphenEdit(limit, wp.getEndHyphenEdit()));
return handleText(wp, start, limit, start, limit, runIsRtl, c, consumer, x, top,
y, bottom, fmi, drawBounds, needWidth, measureLimit, null, advances,
- advancesIndex, lineInfo);
+ advancesIndex, lineInfo, runFlag);
}
// Shaping needs to take into account context up to metric boundaries,
@@ -1554,6 +1671,9 @@
// and use.
activePaint.set(wp);
} else if (!equalAttributes(wp, activePaint)) {
+ final int spanRunFlag = resolveRunFlagForSubSequence(
+ runFlag, runIsRtl, start, measureLimit, activeStart, activeEnd);
+
// The style of the present chunk of text is substantially different from the
// style of the previous chunk. We need to handle the active piece of text
// and restart with the present chunk.
@@ -1565,7 +1685,7 @@
consumer, x, top, y, bottom, fmi, drawBounds,
needWidth || activeEnd < measureLimit,
Math.min(activeEnd, mlimit), mDecorations,
- advances, advancesIndex + activeStart - start, lineInfo);
+ advances, advancesIndex + activeStart - start, lineInfo, spanRunFlag);
activeStart = j;
activePaint.set(wp);
@@ -1585,6 +1705,9 @@
mDecorations.add(copy);
}
}
+
+ final int spanRunFlag = resolveRunFlagForSubSequence(
+ runFlag, runIsRtl, start, measureLimit, activeStart, activeEnd);
// Handle the final piece of text.
activePaint.setStartHyphenEdit(
adjustStartHyphenEdit(activeStart, mPaint.getStartHyphenEdit()));
@@ -1593,7 +1716,7 @@
x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, consumer, x,
top, y, bottom, fmi, drawBounds, needWidth || activeEnd < measureLimit,
Math.min(activeEnd, mlimit), mDecorations,
- advances, advancesIndex + activeStart - start, lineInfo);
+ advances, advancesIndex + activeStart - start, lineInfo, spanRunFlag);
}
return x - originalX;
@@ -1614,7 +1737,6 @@
*/
private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
-
if (mCharsValid) {
int count = end - start;
int contextCount = contextEnd - contextStart;
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 7850554..ba7874e 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -39,8 +39,10 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
+import android.view.flags.Flags;
import dalvik.system.CloseGuard;
+import dalvik.system.VMRuntime;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -101,6 +103,10 @@
long nativeObject, float frameRate, int compatibility, int changeFrameRateStrategy);
private static native void nativeDestroy(long nativeObject);
+ // 5MB is a wild guess for what the average surface should be. On most new phones, a full-screen
+ // surface is about 9MB... but not all surfaces are screen size. This should be a nice balance.
+ private static final long SURFACE_NATIVE_ALLOCATION_SIZE_BYTES = 5_000_000;
+
public static final @android.annotation.NonNull Parcelable.Creator<Surface> CREATOR =
new Parcelable.Creator<Surface>() {
@Override
@@ -331,6 +337,7 @@
*/
@UnsupportedAppUsage
public Surface() {
+ registerNativeMemoryUsage();
}
/**
@@ -343,6 +350,7 @@
*/
public Surface(@NonNull SurfaceControl from) {
copyFrom(from);
+ registerNativeMemoryUsage();
}
/**
@@ -370,6 +378,7 @@
mName = surfaceTexture.toString();
setNativeObjectLocked(nativeCreateFromSurfaceTexture(surfaceTexture));
}
+ registerNativeMemoryUsage();
}
/* called from android_view_Surface_createFromIGraphicBufferProducer() */
@@ -378,6 +387,7 @@
synchronized (mLock) {
setNativeObjectLocked(nativeObject);
}
+ registerNativeMemoryUsage();
}
@Override
@@ -389,6 +399,7 @@
release();
} finally {
super.finalize();
+ freeNativeMemoryUsage();
}
}
@@ -1243,4 +1254,16 @@
return mIsWideColorGamut;
}
}
+
+ private static void registerNativeMemoryUsage() {
+ if (Flags.enableSurfaceNativeAllocRegistration()) {
+ VMRuntime.getRuntime().registerNativeAllocation(SURFACE_NATIVE_ALLOCATION_SIZE_BYTES);
+ }
+ }
+
+ private static void freeNativeMemoryUsage() {
+ if (Flags.enableSurfaceNativeAllocRegistration()) {
+ VMRuntime.getRuntime().registerNativeFree(SURFACE_NATIVE_ALLOCATION_SIZE_BYTES);
+ }
+ }
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 34b46700..57174de 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -26,6 +26,7 @@
import static android.view.InputDevice.SOURCE_CLASS_NONE;
import static android.view.InsetsSource.ID_IME;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
+import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.View.PFLAG_DRAW_ANIMATION;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
@@ -1010,8 +1011,10 @@
// Used to check if there were any view invalidations in
// the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME).
private boolean mHasInvalidation = false;
- // Used to check if it is in the touch boosting period.
+ // Used to check if it is in the frame rate boosting period.
private boolean mIsFrameRateBoosting = false;
+ // Used to check if it is in touch boosting period.
+ private boolean mIsTouchBoosting = false;
// Used to check if there is a message in the message queue
// for idleness handling.
private boolean mHasIdledMessage = false;
@@ -6426,11 +6429,12 @@
* Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME).
*/
mIsFrameRateBoosting = false;
+ mIsTouchBoosting = false;
setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory,
mLastPreferredFrameRateCategory));
break;
case MSG_CHECK_INVALIDATION_IDLE:
- if (!mHasInvalidation && !mIsFrameRateBoosting) {
+ if (!mHasInvalidation && !mIsFrameRateBoosting && !mIsTouchBoosting) {
mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
mHasIdledMessage = false;
@@ -7453,7 +7457,7 @@
// For the variable refresh rate project
if (handled && shouldTouchBoost(action, mWindowAttributes.type)) {
// set the frame rate to the maximum value.
- mIsFrameRateBoosting = true;
+ mIsTouchBoosting = true;
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
}
/**
@@ -12202,8 +12206,16 @@
return;
}
- int frameRateCategory = mIsFrameRateBoosting || mInsetsAnimationRunning
- ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory;
+ int frameRateCategory = mIsTouchBoosting
+ ? FRAME_RATE_CATEGORY_HIGH_HINT : preferredFrameRateCategory;
+
+ // FRAME_RATE_CATEGORY_HIGH has a higher precedence than FRAME_RATE_CATEGORY_HIGH_HINT
+ // For now, FRAME_RATE_CATEGORY_HIGH_HINT is used for boosting with user interaction.
+ // FRAME_RATE_CATEGORY_HIGH is for boosting without user interaction
+ // (e.g., Window Initialization).
+ if (mIsFrameRateBoosting || mInsetsAnimationRunning) {
+ frameRateCategory = FRAME_RATE_CATEGORY_HIGH;
+ }
try {
if (mLastPreferredFrameRateCategory != frameRateCategory) {
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 2b96ee6..2c5fbd7 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -89,4 +89,12 @@
description: "Feature flag to enable a multi-instance system ui component property."
bug: "262864589"
is_fixed_read_only: true
+}
+
+flag {
+ name: "insets_decoupled_configuration"
+ namespace: "windowing_frontend"
+ description: "Configuration decoupled from insets"
+ bug: "151861875"
+ is_fixed_read_only: true
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/os/MonotonicClock.java b/core/java/com/android/internal/os/MonotonicClock.java
index c3bcfa6..eca6f58 100644
--- a/core/java/com/android/internal/os/MonotonicClock.java
+++ b/core/java/com/android/internal/os/MonotonicClock.java
@@ -17,11 +17,11 @@
package com.android.internal.os;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.util.AtomicFile;
import android.util.Log;
import android.util.Xml;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -49,20 +49,27 @@
private final AtomicFile mFile;
private final Clock mClock;
- private long mTimeshift;
+ private final long mTimeshift;
public static final long UNDEFINED = -1;
public MonotonicClock(File file) {
- mFile = new AtomicFile(file);
- mClock = Clock.SYSTEM_CLOCK;
- read();
+ this (file, Clock.SYSTEM_CLOCK.elapsedRealtime(), Clock.SYSTEM_CLOCK);
}
public MonotonicClock(long monotonicTime, @NonNull Clock clock) {
+ this(null, monotonicTime, clock);
+ }
+
+ public MonotonicClock(@Nullable File file, long monotonicTime, @NonNull Clock clock) {
mClock = clock;
- mFile = null;
- mTimeshift = monotonicTime - mClock.elapsedRealtime();
+ if (file != null) {
+ mFile = new AtomicFile(file);
+ mTimeshift = read(monotonicTime - mClock.elapsedRealtime());
+ } else {
+ mFile = null;
+ mTimeshift = monotonicTime - mClock.elapsedRealtime();
+ }
}
/**
@@ -81,15 +88,16 @@
return mTimeshift + elapsedRealtimeMs;
}
- private void read() {
+ private long read(long defaultTimeshift) {
if (!mFile.exists()) {
- return;
+ return defaultTimeshift;
}
try {
- readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser());
+ return readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser());
} catch (IOException e) {
Log.e(TAG, "Cannot load monotonic clock from " + mFile.getBaseFile(), e);
+ return defaultTimeshift;
}
}
@@ -102,18 +110,21 @@
return;
}
- try (FileOutputStream out = mFile.startWrite()) {
+ FileOutputStream out = null;
+ try {
+ out = mFile.startWrite();
writeXml(out, Xml.newBinarySerializer());
+ mFile.finishWrite(out);
} catch (IOException e) {
Log.e(TAG, "Cannot write monotonic clock to " + mFile.getBaseFile(), e);
+ mFile.failWrite(out);
}
}
/**
* Parses an XML file containing the persistent state of the monotonic clock.
*/
- @VisibleForTesting
- public void readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException {
+ private long readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException {
long savedTimeshift = 0;
try {
parser.setInput(inputStream, StandardCharsets.UTF_8.name());
@@ -128,14 +139,13 @@
} catch (XmlPullParserException e) {
throw new IOException(e);
}
- mTimeshift = savedTimeshift - mClock.elapsedRealtime();
+ return savedTimeshift - mClock.elapsedRealtime();
}
/**
* Creates an XML file containing the persistent state of the monotonic clock.
*/
- @VisibleForTesting
- public void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException {
+ private void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException {
serializer.setOutput(out, StandardCharsets.UTF_8.name());
serializer.startDocument(null, true);
serializer.startTag(null, XML_TAG_MONOTONIC_TIME);
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index b87027c..4191936 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -120,8 +120,9 @@
public static @NonNull <I, O> List<O> map(@Nullable List<I> cur,
Function<? super I, ? extends O> f) {
if (isEmpty(cur)) return Collections.emptyList();
- final ArrayList<O> result = new ArrayList<>();
- for (int i = 0; i < cur.size(); i++) {
+ final int size = cur.size();
+ final ArrayList<O> result = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
result.add(f.apply(cur.get(i)));
}
return result;
@@ -133,7 +134,7 @@
public static @NonNull <I, O> Set<O> map(@Nullable Set<I> cur,
Function<? super I, ? extends O> f) {
if (isEmpty(cur)) return emptySet();
- ArraySet<O> result = new ArraySet<>();
+ ArraySet<O> result = new ArraySet<>(cur.size());
if (cur instanceof ArraySet) {
ArraySet<I> arraySet = (ArraySet<I>) cur;
int size = arraySet.size();
@@ -163,7 +164,7 @@
Function<? super I, ? extends O> f) {
if (isEmpty(cur)) return Collections.emptyList();
List<O> result = null;
- for (int i = 0; i < cur.size(); i++) {
+ for (int i = 0, size = cur.size(); i < size; i++) {
O transformed = f.apply(cur.get(i));
if (transformed != null) {
result = add(result, transformed);
@@ -239,7 +240,7 @@
public static @NonNull <T> List<T> filter(@Nullable List<?> list, Class<T> c) {
if (isEmpty(list)) return Collections.emptyList();
ArrayList<T> result = null;
- for (int i = 0; i < list.size(); i++) {
+ for (int i = 0, size = list.size(); i < size; i++) {
final Object item = list.get(i);
if (c.isInstance(item)) {
result = ArrayUtils.add(result, (T) item);
@@ -273,7 +274,7 @@
public static @Nullable <T> T find(@Nullable List<T> items,
java.util.function.Predicate<T> predicate) {
if (isEmpty(items)) return null;
- for (int i = 0; i < items.size(); i++) {
+ for (int i = 0, size = items.size(); i < size; i++) {
final T item = items.get(i);
if (predicate.test(item)) return item;
}
diff --git a/core/java/com/android/internal/util/RingBuffer.java b/core/java/com/android/internal/util/RingBuffer.java
index 0488659..342ba1b6 100644
--- a/core/java/com/android/internal/util/RingBuffer.java
+++ b/core/java/com/android/internal/util/RingBuffer.java
@@ -19,7 +19,10 @@
import static com.android.internal.util.Preconditions.checkArgumentPositive;
import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
+import java.util.function.IntFunction;
+import java.util.function.Supplier;
/**
* A simple ring buffer structure with bounded capacity backed by an array.
@@ -30,16 +33,35 @@
@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class RingBuffer<T> {
+ private final Supplier<T> mNewItem;
// Array for storing events.
private final T[] mBuffer;
// Cursor keeping track of the logical end of the array. This cursor never
// wraps and instead keeps track of the total number of append() operations.
private long mCursor = 0;
+ /**
+ * @deprecated This uses reflection to create new instances.
+ * Use {@link #RingBuffer(Supplier, IntFunction, int)}} instead.
+ */
+ @Deprecated
public RingBuffer(Class<T> c, int capacity) {
+ this(() -> (T) createNewItem(c), cap -> (T[]) Array.newInstance(c, cap), capacity);
+ }
+
+ private static Object createNewItem(Class c) {
+ try {
+ return c.getDeclaredConstructor().newInstance();
+ } catch (IllegalAccessException | InstantiationException | NoSuchMethodException
+ | InvocationTargetException e) {
+ return null;
+ }
+ }
+
+ public RingBuffer(Supplier<T> newItem, IntFunction<T[]> newBacking, int capacity) {
checkArgumentPositive(capacity, "A RingBuffer cannot have 0 capacity");
- // Java cannot create generic arrays without a runtime hint.
- mBuffer = (T[]) Array.newInstance(c, capacity);
+ mBuffer = newBacking.apply(capacity);
+ mNewItem = newItem;
}
public int size() {
@@ -69,22 +91,11 @@
public T getNextSlot() {
final int nextSlotIdx = indexOf(mCursor++);
if (mBuffer[nextSlotIdx] == null) {
- mBuffer[nextSlotIdx] = createNewItem();
+ mBuffer[nextSlotIdx] = mNewItem.get();
}
return mBuffer[nextSlotIdx];
}
- /**
- * @return a new object of type <T> or null if a new object could not be created.
- */
- protected T createNewItem() {
- try {
- return (T) mBuffer.getClass().getComponentType().newInstance();
- } catch (IllegalAccessException | InstantiationException e) {
- return null;
- }
- }
-
public T[] toArray() {
// Only generic way to create a T[] from another T[]
T[] out = Arrays.copyOf(mBuffer, size(), (Class<T[]>) mBuffer.getClass());
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5f3f641..9c455d52 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7896,6 +7896,26 @@
<permission android:name="android.permission.RECEIVE_SENSITIVE_NOTIFICATIONS"
android:protectionLevel="signature|role" />
+ <!-- @SystemApi
+ @FlaggedApi("android.app.bic_client")
+ Allows app to call BackgroundInstallControlManager API to retrieve silently installed apps
+ for all users on device.
+ <p>Apps with a BackgroundInstallControlManager client will not be able to call any API without
+ this permission.
+ <p>Protection level: signature|role
+ @hide
+ -->
+ <permission android:name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi Allows an application to read the system grammatical gender.
+ @FlaggedApi("android.app.system_terms_of_address_enabled")
+ <p>Protection level: signature|privileged|appop
+ @hide
+ -->
+ <permission android:name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"
+ android:protectionLevel="signature|privileged|appop"/>
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml
index f3acab0..fe12f6e 100644
--- a/core/res/res/drawable-nodpi/platlogo.xml
+++ b/core/res/res/drawable-nodpi/platlogo.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2021 The Android Open Source Project
+Copyright (C) 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -20,179 +20,103 @@
android:viewportWidth="512"
android:viewportHeight="512">
<path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0">
+ android:pathData="M256.22,437.7c-26.38,0 -43.81,-18.3 -61.2,-48.42 -17.39,-30.12 -103.26,-178.86 -120.65,-208.98s-24.52,-54.37 -11.33,-77.21c13.19,-22.84 37.75,-28.79 72.53,-28.79 34.78,0 206.53,0 241.31,0 34.78,0 59.35,5.95 72.53,28.79 13.19,22.84 6.06,47.09 -11.33,77.21s-103.26,178.86 -120.65,208.98c-17.39,30.12 -34.83,48.42 -61.2,48.42Z"
+ android:strokeWidth="0">
<aapt:attr name="android:fillColor">
<gradient
- android:startX="256"
- android:startY="21.81"
- android:endX="256"
- android:endY="350.42"
+ android:startX="56.22"
+ android:startY="256"
+ android:endX="456.22"
+ android:endY="256"
android:type="linear">
<item android:offset="0" android:color="#FF073042"/>
<item android:offset="1" android:color="#FF073042"/>
</gradient>
</aapt:attr>
</path>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="m195.27,187.64l2.25,-6.69c13.91,78.13 50.84,284.39 50.84,50.33 0,-0.97 0.72,-1.81 1.62,-1.81h12.69c0.9,0 1.62,0.83 1.62,1.8 -0.2,409.91 -69.03,-43.64 -69.03,-43.64Z"
- android:fillColor="#3ddc84"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="m158.77,180.68l-33.17,57.45c-1.9,3.3 -0.77,7.52 2.53,9.42 3.3,1.9 7.52,0.77 9.42,-2.53l33.59,-58.17c54.27,24.33 116.34,24.33 170.61,0l33.59,58.17c1.97,3.26 6.21,4.3 9.47,2.33 3.17,-1.91 4.26,-5.99 2.47,-9.23l-33.16,-57.45c56.95,-30.97 95.91,-88.64 101.61,-156.76H57.17c5.7,68.12 44.65,125.79 101.61,156.76Z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M250.26,187.66L262.17,187.66A2.13,2.13 0,0 1,264.3 189.78L264.3,217.85A2.13,2.13 0,0 1,262.17 219.98L250.26,219.98A2.13,2.13 0,0 1,248.14 217.85L248.14,189.78A2.13,2.13 0,0 1,250.26 187.66z"
- android:fillColor="#3ddc84"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M250.12,170.29L262.32,170.29A1.98,1.98 0,0 1,264.3 172.26L264.3,176.39A1.98,1.98 0,0 1,262.32 178.37L250.12,178.37A1.98,1.98 0,0 1,248.14 176.39L248.14,172.26A1.98,1.98 0,0 1,250.12 170.29z"
- android:fillColor="#3ddc84"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M171.92,216.82h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M188.8,275.73h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M369.04,337.63h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M285.93,252.22h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M318.96,218.84h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M294.05,288.55h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M330.82,273.31h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M188.8,298.95h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M220.14,238.94h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M272.1,318.9h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M293.34,349.25h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M161.05,254.24h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M378.92,192h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M137.87,323.7h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
<path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"
- android:strokeWidth="56.561"
- android:fillColor="#00000000"
- android:strokeColor="#f86734"/>
- <path
- android:pathData="m256.22,126.57c-6.69,0 -12.12,5.27 -12.12,11.77v14.52c0,1.25 1.02,2.27 2.27,2.27h0c1.25,0 2.27,-1.02 2.27,-2.27v-3.91c0,-2.51 2.04,-4.55 4.55,-4.55h6.06c2.51,0 4.55,2.04 4.55,4.55v3.91c0,1.25 1.02,2.27 2.27,2.27s2.27,-1.02 2.27,-2.27v-14.52c0,-6.5 -5.43,-11.77 -12.12,-11.77Z"
+ android:pathData="M198.85,168.57l2.03,-6.03c12.55,70.48 45.87,256.56 45.87,45.41 0,-0.88 0.65,-1.63 1.46,-1.63h11.45c0.81,0 1.46,0.75 1.46,1.63 -0.18,369.8 -62.28,-39.37 -62.28,-39.37Z"
+ android:strokeWidth="0"
android:fillColor="#3ddc84"/>
<path
- android:pathData="m93.34,116.36l3.85,-4.36 29.64,9.76 -4.44,5.03 -6.23,-2.1 -7.86,8.91 2.86,5.92 -4.43,5.03 -13.39,-28.18ZM110.43,122.76l-8.86,-3.02 4.11,8.41 4.76,-5.39Z"
+ android:pathData="M186.69,167.97l-23.69,41.03c-1.36,2.36 -0.55,5.37 1.8,6.73 2.36,1.36 5.37,0.55 6.73,-1.8l23.99,-41.55c38.76,17.38 83.1,17.38 121.86,0l23.99,41.55c1.41,2.33 4.44,3.07 6.77,1.66 2.26,-1.37 3.04,-4.28 1.76,-6.59l-23.69,-41.03c40.68,-22.12 68.5,-63.31 72.57,-111.97H114.11c4.07,48.65 31.89,89.85 72.57,111.97Z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
<path
- android:pathData="m153.62,100.85l-21.71,-6.2 10.38,14.38 -5.21,3.76 -16.78,-23.26 4.49,-3.24 21.65,6.19 -10.35,-14.35 5.24,-3.78 16.78,23.26 -4.49,3.24Z"
+ android:pathData="M248.46,168.59L259.2,168.59A1.92,1.92 0,0 1,261.12 170.5L261.12,195.83A1.92,1.92 0,0 1,259.2 197.75L248.46,197.75A1.92,1.92 0,0 1,246.54 195.83L246.54,170.5A1.92,1.92 0,0 1,248.46 168.59z"
+ android:strokeWidth="0"
+ android:fillColor="#3ddc84"/>
+ <path
+ android:pathData="M248.32,152.92L259.34,152.92A1.78,1.78 0,0 1,261.12 154.7L261.12,158.43A1.78,1.78 0,0 1,259.34 160.21L248.32,160.21A1.78,1.78 0,0 1,246.54 158.43L246.54,154.7A1.78,1.78 0,0 1,248.32 152.92z"
+ android:strokeWidth="0"
+ android:fillColor="#3ddc84"/>
+ <path
+ android:pathData="M159.03,176.91h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
<path
- android:pathData="m161.46,63.15l8.99,-3.84c7.43,-3.18 15.96,0.12 19.09,7.44 3.13,7.32 -0.38,15.76 -7.81,18.94l-8.99,3.84 -11.28,-26.38ZM179.41,80.26c4.46,-1.91 5.96,-6.72 4.15,-10.96 -1.81,-4.24 -6.33,-6.48 -10.79,-4.57l-3.08,1.32 6.64,15.53 3.08,-1.32Z"
+ android:pathData="M188.8,275.73h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
<path
- android:pathData="m204.23,47.57l11.1,-2.2c5.31,-1.05 9.47,2.08 10.4,6.76 0.72,3.65 -0.76,6.37 -4.07,8.34l12.4,10.44 -7.57,1.5 -11.65,-9.76 -1.03,0.2 2.3,11.61 -6.3,1.25 -5.57,-28.14ZM216.78,56.7c1.86,-0.37 3,-1.71 2.68,-3.33 -0.34,-1.7 -1.88,-2.43 -3.74,-2.06l-4.04,0.8 1.07,5.39 4.04,-0.8Z"
+ android:pathData="M373.41,158.93h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
<path
- android:pathData="m244.29,55.6c0.13,-8.16 6.86,-14.72 15.06,-14.58 8.16,0.13 14.72,6.9 14.58,15.06s-6.91,14.72 -15.06,14.58c-8.2,-0.13 -14.71,-6.9 -14.58,-15.06ZM267.44,55.98c0.08,-4.64 -3.54,-8.66 -8.18,-8.74 -4.68,-0.08 -8.42,3.82 -8.5,8.47 -0.08,4.65 3.54,8.66 8.22,8.74 4.64,0.08 8.39,-3.82 8.46,-8.47Z"
+ android:pathData="M112.1,129.34h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
<path
- android:pathData="m294.39,44.84l6.31,1.23 -5.49,28.16 -6.31,-1.23 5.49,-28.16Z"
+ android:pathData="M285.93,252.22h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
<path
- android:pathData="m321.94,51.41l9.14,3.48c7.55,2.88 11.39,11.17 8.56,18.61 -2.83,7.44 -11.22,11.07 -18.77,8.19l-9.14,-3.48 10.22,-26.8ZM322.96,76.19c4.53,1.73 8.95,-0.69 10.6,-5 1.64,-4.3 -0.05,-9.06 -4.58,-10.78l-3.13,-1.19 -6.01,15.78 3.13,1.19Z"
+ android:pathData="M318.96,218.84h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
<path
- android:pathData="m381.41,89.24l-4.21,-3.21 3.65,-4.78 9.06,6.91 -17.4,22.81 -4.85,-3.7 13.75,-18.02Z"
+ android:pathData="M294.05,288.55h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
<path
- android:pathData="m397.96,126.37l-9.56,-10.26 3.61,-3.36 22.8,-1.25 3.74,4.02 -12.35,11.51 2.51,2.69 -4.08,3.8 -2.51,-2.69 -4.55,4.24 -4.16,-4.46 4.55,-4.24ZM407.83,117.17l-10.28,0.58 4.49,4.82 5.79,-5.4Z"
+ android:pathData="M330.82,263.31h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
+ <path
+ android:pathData="M188.8,298.95h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M224.18,216.82h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M272.1,318.9h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M293.34,339.25h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M165.09,236.28h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M378.92,192h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M204.28,314.86h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M253.83,118.47c-6.04,0 -10.93,4.76 -10.93,10.62v13.1c0,1.13 0.92,2.05 2.05,2.05h0c1.13,0 2.05,-0.92 2.05,-2.05v-3.53c0,-2.27 1.84,-4.1 4.1,-4.1h5.47c2.27,0 4.1,1.84 4.1,4.1v3.53c0,1.13 0.92,2.05 2.05,2.05s2.05,-0.92 2.05,-2.05v-13.1c0,-5.86 -4.9,-10.62 -10.93,-10.62Z"
+ android:strokeWidth="0"
+ android:fillColor="#3ddc84"/>
+ <path
+ android:pathData="M256,437.7c-26.38,0 -43.81,-18.3 -61.2,-48.42 -17.39,-30.12 -103.26,-178.86 -120.65,-208.98 -17.39,-30.12 -24.52,-54.37 -11.33,-77.21 13.19,-22.84 37.75,-28.79 72.53,-28.79 34.78,0 206.53,0 241.31,0 34.78,0 59.35,5.95 72.53,28.79 13.19,22.84 6.06,47.09 -11.33,77.21 -17.39,30.12 -103.26,178.86 -120.65,208.98 -17.39,30.12 -34.83,48.42 -61.2,48.42Z"
+ android:strokeWidth="55"
+ android:fillColor="#00000000"
+ android:strokeColor="#f86733"/>
</vector>
-
diff --git a/core/tests/BroadcastRadioTests/TEST_MAPPING b/core/tests/BroadcastRadioTests/TEST_MAPPING
new file mode 100644
index 0000000..b085a27
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "BroadcastRadioTests"
+ }
+ ]
+}
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index 1617eda..e32a57b 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -48,7 +48,7 @@
@get:Rule
val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
- private lateinit var defaultLookupTables: SparseArray<FontScaleConverter>
+ private var defaultLookupTables: SparseArray<FontScaleConverter>? = null
@Before
fun setup() {
@@ -58,7 +58,9 @@
@After
fun teardown() {
// Restore the default tables (since some tests will have added extras to the cache)
- FontScaleConverterFactory.sLookupTables = defaultLookupTables
+ if (defaultLookupTables != null) {
+ FontScaleConverterFactory.sLookupTables = defaultLookupTables!!
+ }
}
@Test
diff --git a/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
new file mode 100644
index 0000000..27869bb
--- /dev/null
+++ b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text
+
+import android.graphics.Paint
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+const val LEFT_EDGE = Paint.TEXT_RUN_FLAG_LEFT_EDGE
+const val RIGHT_EDGE = Paint.TEXT_RUN_FLAG_RIGHT_EDGE
+const val MIDDLE_OF_LINE = 0
+const val WHOLE_LINE = LEFT_EDGE or RIGHT_EDGE
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TextLineLetterSpacingTest {
+
+ @Rule
+ @JvmField
+ val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ @Test
+ fun calculateRunFlagTest() {
+ // Only one Bidi run
+ assertThat(TextLine.calculateRunFlag(0, 1, Layout.DIR_LEFT_TO_RIGHT))
+ .isEqualTo(WHOLE_LINE)
+ assertThat(TextLine.calculateRunFlag(0, 1, Layout.DIR_RIGHT_TO_LEFT))
+ .isEqualTo(WHOLE_LINE)
+
+ // Two BiDi Runs.
+ // If the layout is LTR, the first run is the left most run.
+ assertThat(TextLine.calculateRunFlag(0, 2, Layout.DIR_LEFT_TO_RIGHT))
+ .isEqualTo(LEFT_EDGE)
+ // If the layout is LTR, the last run is the right most run.
+ assertThat(TextLine.calculateRunFlag(1, 2, Layout.DIR_LEFT_TO_RIGHT))
+ .isEqualTo(RIGHT_EDGE)
+ // If the layout is RTL, the first run is the right most run.
+ assertThat(TextLine.calculateRunFlag(0, 2, Layout.DIR_RIGHT_TO_LEFT))
+ .isEqualTo(RIGHT_EDGE)
+ // If the layout is RTL, the last run is the left most run.
+ assertThat(TextLine.calculateRunFlag(1, 2, Layout.DIR_RIGHT_TO_LEFT))
+ .isEqualTo(LEFT_EDGE)
+
+ // Three BiDi Runs.
+ // If the layout is LTR, the first run is the left most run.
+ assertThat(TextLine.calculateRunFlag(0, 3, Layout.DIR_LEFT_TO_RIGHT))
+ .isEqualTo(LEFT_EDGE)
+ // Regardless of the context direction, the middle run must not have any flags.
+ assertThat(TextLine.calculateRunFlag(1, 3, Layout.DIR_LEFT_TO_RIGHT))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // If the layout is LTR, the last run is the right most run.
+ assertThat(TextLine.calculateRunFlag(2, 3, Layout.DIR_LEFT_TO_RIGHT))
+ .isEqualTo(RIGHT_EDGE)
+ // If the layout is RTL, the first run is the right most run.
+ assertThat(TextLine.calculateRunFlag(0, 3, Layout.DIR_RIGHT_TO_LEFT))
+ .isEqualTo(RIGHT_EDGE)
+ // Regardless of the context direction, the middle run must not have any flags.
+ assertThat(TextLine.calculateRunFlag(1, 3, Layout.DIR_RIGHT_TO_LEFT))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // If the layout is RTL, the last run is the left most run.
+ assertThat(TextLine.calculateRunFlag(2, 3, Layout.DIR_RIGHT_TO_LEFT))
+ .isEqualTo(LEFT_EDGE)
+ }
+
+ @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ @Test
+ fun resolveRunFlagForSubSequenceTest() {
+ val runStart = 5
+ val runEnd = 15
+ // Regardless of the run directions, if the span covers entire Bidi run, the same runFlag
+ // should be returned.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ LEFT_EDGE, false, runStart, runEnd, runStart, runEnd))
+ .isEqualTo(LEFT_EDGE)
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ LEFT_EDGE, true, runStart, runEnd, runStart, runEnd))
+ .isEqualTo(LEFT_EDGE)
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ RIGHT_EDGE, false, runStart, runEnd, runStart, runEnd))
+ .isEqualTo(RIGHT_EDGE)
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ RIGHT_EDGE, true, runStart, runEnd, runStart, runEnd))
+ .isEqualTo(RIGHT_EDGE)
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ WHOLE_LINE, false, runStart, runEnd, runStart, runEnd))
+ .isEqualTo(WHOLE_LINE)
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ WHOLE_LINE, true, runStart, runEnd, runStart, runEnd))
+ .isEqualTo(WHOLE_LINE)
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ MIDDLE_OF_LINE, false, runStart, runEnd, runStart, runEnd))
+ .isEqualTo(MIDDLE_OF_LINE)
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ MIDDLE_OF_LINE, true, runStart, runEnd, runStart, runEnd))
+ .isEqualTo(MIDDLE_OF_LINE)
+
+
+
+ // Left edge of LTR text, span start from run start offset but not cover the run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ LEFT_EDGE, false, runStart, runEnd, runStart, runEnd - 1))
+ .isEqualTo(LEFT_EDGE)
+ // Left edge of RTL text, span start from run start offset but not cover the run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ LEFT_EDGE, true, runStart, runEnd, runStart, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Right edge of LTR text, span start from run start offset but not cover the run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ RIGHT_EDGE, false, runStart, runEnd, runStart, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Right edge of RTL text, span start from run start offset but not cover the run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ RIGHT_EDGE, true, runStart, runEnd, runStart, runEnd - 1))
+ .isEqualTo(RIGHT_EDGE)
+ // Whole line of LTR text, span start from run start offset but not cover the run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ WHOLE_LINE, false, runStart, runEnd, runStart, runEnd - 1))
+ .isEqualTo(LEFT_EDGE)
+ // Whole line of RTL text, span start from run start offset but not cover the run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ WHOLE_LINE, true, runStart, runEnd, runStart, runEnd - 1))
+ .isEqualTo(RIGHT_EDGE)
+ // Middle of LTR text, span start from run start offset but not cover the run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ MIDDLE_OF_LINE, false, runStart, runEnd, runStart, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Middle of RTL text, span start from run start offset but not cover the run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ MIDDLE_OF_LINE, true, runStart, runEnd, runStart, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+
+
+
+ // Left edge of LTR text, span start from middle of run start offset until end of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ LEFT_EDGE, false, runStart, runEnd, runStart + 1, runEnd))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Left edge of RTL text, span start from middle of run start offset until end of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ LEFT_EDGE, true, runStart, runEnd, runStart + 1, runEnd))
+ .isEqualTo(LEFT_EDGE)
+ // Right edge of LTR text, span start from middle of run start offset until end of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ RIGHT_EDGE, false, runStart, runEnd, runStart + 1, runEnd))
+ .isEqualTo(RIGHT_EDGE)
+ // Right edge of RTL text, span start from middle of run start offset until end of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ RIGHT_EDGE, true, runStart, runEnd, runStart + 1, runEnd))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Whole line of LTR text, span start from middle of run start offset until end of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ WHOLE_LINE, false, runStart, runEnd, runStart + 1, runEnd))
+ .isEqualTo(RIGHT_EDGE)
+ // Whole line of RTL text, span start from middle of run start offset until end of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ WHOLE_LINE, true, runStart, runEnd, runStart + 1, runEnd))
+ .isEqualTo(LEFT_EDGE)
+ // Middle of LTR text, span start from middle of run start offset until end of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ MIDDLE_OF_LINE, false, runStart, runEnd, runStart + 1, runEnd))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Middle of RTL text, span start from middle of run start offset until end of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ MIDDLE_OF_LINE, true, runStart, runEnd, runStart + 1, runEnd))
+ .isEqualTo(MIDDLE_OF_LINE)
+
+
+
+ // Left edge of LTR text, span start from middle of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ LEFT_EDGE, false, runStart, runEnd, runStart + 1, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Left edge of RTL text, span start from middle of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ LEFT_EDGE, true, runStart, runEnd, runStart + 1, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Right edge of LTR text, span start from middle of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ RIGHT_EDGE, false, runStart, runEnd, runStart + 1, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Right edge of RTL text, span start from middle of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ RIGHT_EDGE, true, runStart, runEnd, runStart + 1, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Whole line of LTR text, span start from middle of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ WHOLE_LINE, false, runStart, runEnd, runStart + 1, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Whole line of RTL text, span start from middle of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ WHOLE_LINE, true, runStart, runEnd, runStart + 1, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Middle of LTR text, span start from middle of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ MIDDLE_OF_LINE, false, runStart, runEnd, runStart + 1, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Middle of RTL text, span start from middle of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ MIDDLE_OF_LINE, true, runStart, runEnd, runStart + 1, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index cf3eb12..cfbda84 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -19,6 +19,7 @@
import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
+import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
@@ -575,8 +576,13 @@
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+ FRAME_RATE_CATEGORY_HIGH_HINT);
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
diff --git a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
index 0742052..ec4c563 100644
--- a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
@@ -18,39 +18,67 @@
import static com.google.common.truth.Truth.assertThat;
-import android.util.Xml;
-
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileWriter;
import java.io.IOException;
+import java.nio.file.Files;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class MonotonicClockTest {
private final MockClock mClock = new MockClock();
+ private File mFile;
+
+ @Before
+ public void setup() throws IOException {
+ File systemDir = Files.createTempDirectory("MonotonicClockTest").toFile();
+ mFile = new File(systemDir, "test_monotonic_clock.xml");
+ if (mFile.exists()) {
+ assertThat(mFile.delete()).isTrue();
+ }
+ }
@Test
public void persistence() throws IOException {
- MonotonicClock monotonicClock = new MonotonicClock(1000, mClock);
+ MonotonicClock monotonicClock = new MonotonicClock(mFile, 1000, mClock);
mClock.realtime = 234;
assertThat(monotonicClock.monotonicTime()).isEqualTo(1234);
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- monotonicClock.writeXml(out, Xml.newBinarySerializer());
+ monotonicClock.write();
mClock.realtime = 42;
- MonotonicClock newMonotonicClock = new MonotonicClock(0, mClock);
- ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
- newMonotonicClock.readXml(in, Xml.newBinaryPullParser());
+ MonotonicClock newMonotonicClock = new MonotonicClock(mFile, 0, mClock);
mClock.realtime = 2000;
assertThat(newMonotonicClock.monotonicTime()).isEqualTo(1234 - 42 + 2000);
}
+
+ @Test
+ public void constructor() {
+ MonotonicClock monotonicClock = new MonotonicClock(null, 1000, mClock);
+ mClock.realtime = 234;
+
+ assertThat(monotonicClock.monotonicTime()).isEqualTo(1234);
+ }
+
+ @Test
+ public void corruptedFile() throws IOException {
+ // Create an invalid binary XML file to cause IOException: "Unexpected magic number"
+ try (FileWriter w = new FileWriter(mFile)) {
+ w.write("garbage");
+ }
+
+ MonotonicClock monotonicClock = new MonotonicClock(mFile, 1000, mClock);
+ mClock.realtime = 234;
+
+ assertThat(monotonicClock.monotonicTime()).isEqualTo(1234);
+ }
}
diff --git a/data/etc/com.android.documentsui.xml b/data/etc/com.android.documentsui.xml
index d32cbec..2e521e3 100644
--- a/data/etc/com.android.documentsui.xml
+++ b/data/etc/com.android.documentsui.xml
@@ -23,5 +23,7 @@
<permission name="android.permission.MODIFY_QUIET_MODE"/>
<permission name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
<permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"/>
+ <!-- Permissions required for reading device configs -->
+ <permission name="android.permission.READ_DEVICE_CONFIG" />
</privapp-permissions>
</permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index a1ea2b8..4be75f8 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -426,6 +426,7 @@
<!-- Permissions required for CTS test - android.server.biometrics -->
<permission name="android.permission.USE_BIOMETRIC" />
<permission name="android.permission.TEST_BIOMETRIC" />
+ <permission name="android.permission.MANAGE_BIOMETRIC_DIALOG" />
<!-- Permissions required for CTS test - CtsContactsProviderTestCases -->
<permission name="android.contacts.permission.MANAGE_SIM_ACCOUNTS" />
<!-- Permissions required for CTS test - CtsHdmiCecHostTestCases -->
@@ -541,6 +542,8 @@
<permission name="android.permission.GET_BINDING_UID_IMPORTANCE"/>
<!-- Permission required for CTS test NotificationManagerZenTest -->
<permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
+ <!-- Permission required for BinaryTransparencyService shell API and host test -->
+ <permission name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/data/fonts/Android.bp b/data/fonts/Android.bp
index 3dd9ba9..471acaa 100644
--- a/data/fonts/Android.bp
+++ b/data/fonts/Android.bp
@@ -48,12 +48,35 @@
// Copies the font configuration file into system/etc for the product as fonts.xml.
// Additional fonts should be installed to /product/fonts/ alongside a corresponding
// fonts_customiztion.xml in /product/etc/
-prebuilt_etc {
- name: "fonts.xml",
- src: "fonts.xml",
+
+soong_config_bool_variable {
+ name: "use_var_font",
}
-prebuilt_etc {
+soong_config_module_type {
+ name: "prebuilt_fonts_xml",
+ module_type: "prebuilt_etc",
+ config_namespace: "noto_sans_cjk_config",
+ bool_variables: ["use_var_font"],
+ properties: ["src"],
+}
+
+prebuilt_fonts_xml {
+ name: "fonts.xml",
+ src: "fonts.xml",
+ soong_config_variables: {
+ use_var_font: {
+ src: "fonts_cjkvf.xml",
+ },
+ },
+}
+
+prebuilt_fonts_xml {
name: "font_fallback.xml",
src: "font_fallback.xml",
+ soong_config_variables: {
+ use_var_font: {
+ src: "font_fallback_cjkvf.xml",
+ },
+ },
}
diff --git a/data/fonts/font_fallback_cjkvf.xml b/data/fonts/font_fallback_cjkvf.xml
new file mode 100644
index 0000000..70474ba
--- /dev/null
+++ b/data/fonts/font_fallback_cjkvf.xml
@@ -0,0 +1,1015 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ In this file, all fonts without names are added to the default list.
+ Fonts are chosen based on a match: full BCP-47 language tag including
+ script, then just language, and finally order (the first font containing
+ the glyph).
+
+ Order of appearance is also the tiebreaker for weight matching. This is
+ the reason why the 900 weights of Roboto precede the 700 weights - we
+ prefer the former when an 800 weight is requested. Since bold spans
+ effectively add 300 to the weight, this ensures that 900 is the bold
+ paired with the 500 weight, ensuring adequate contrast.
+
+ TODO(rsheeter) update comment; ordering to match 800 to 900 is no longer required
+-->
+<familyset version="23">
+ <!-- first font is default -->
+ <family name="sans-serif" varFamilyType="2">
+ <font>Roboto-Regular.ttf
+ <axis tag="wdth" stylevalue="100" />
+ </font>
+ </family>
+
+
+ <!-- Note that aliases must come after the fonts they reference. -->
+ <alias name="sans-serif-thin" to="sans-serif" weight="100" />
+ <alias name="sans-serif-light" to="sans-serif" weight="300" />
+ <alias name="sans-serif-medium" to="sans-serif" weight="500" />
+ <alias name="sans-serif-black" to="sans-serif" weight="900" />
+ <alias name="arial" to="sans-serif" />
+ <alias name="helvetica" to="sans-serif" />
+ <alias name="tahoma" to="sans-serif" />
+ <alias name="verdana" to="sans-serif" />
+
+ <family name="sans-serif-condensed" varFamilyType="2">
+ <font>Roboto-Regular.ttf
+ <axis tag="wdth" stylevalue="75" />
+ </font>
+ </family>
+ <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" />
+ <alias name="sans-serif-condensed-medium" to="sans-serif-condensed" weight="500" />
+
+ <family name="serif">
+ <font weight="400" style="normal" postScriptName="NotoSerif">NotoSerif-Regular.ttf</font>
+ <font weight="700" style="normal">NotoSerif-Bold.ttf</font>
+ <font weight="400" style="italic">NotoSerif-Italic.ttf</font>
+ <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font>
+ </family>
+ <alias name="serif-bold" to="serif" weight="700" />
+ <alias name="times" to="serif" />
+ <alias name="times new roman" to="serif" />
+ <alias name="palatino" to="serif" />
+ <alias name="georgia" to="serif" />
+ <alias name="baskerville" to="serif" />
+ <alias name="goudy" to="serif" />
+ <alias name="fantasy" to="serif" />
+ <alias name="ITC Stone Serif" to="serif" />
+
+ <family name="monospace">
+ <font weight="400" style="normal">DroidSansMono.ttf</font>
+ </family>
+ <alias name="sans-serif-monospace" to="monospace" />
+ <alias name="monaco" to="monospace" />
+
+ <family name="serif-monospace">
+ <font weight="400" style="normal" postScriptName="CutiveMono-Regular">CutiveMono.ttf</font>
+ </family>
+ <alias name="courier" to="serif-monospace" />
+ <alias name="courier new" to="serif-monospace" />
+
+ <family name="casual">
+ <font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font>
+ </family>
+
+ <family name="cursive" varFamilyType="1">
+ <font>DancingScript-Regular.ttf</font>
+ </family>
+
+ <family name="sans-serif-smallcaps">
+ <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
+ </family>
+
+ <family name="source-sans-pro">
+ <font weight="400" style="normal">SourceSansPro-Regular.ttf</font>
+ <font weight="400" style="italic">SourceSansPro-Italic.ttf</font>
+ <font weight="600" style="normal">SourceSansPro-SemiBold.ttf</font>
+ <font weight="600" style="italic">SourceSansPro-SemiBoldItalic.ttf</font>
+ <font weight="700" style="normal">SourceSansPro-Bold.ttf</font>
+ <font weight="700" style="italic">SourceSansPro-BoldItalic.ttf</font>
+ </family>
+ <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
+
+ <family name="roboto-flex" varFamilyType="2">
+ <font>RobotoFlex-Regular.ttf
+ <axis tag="wdth" stylevalue="100" />
+ </font>
+ </family>
+
+ <!-- fallback fonts -->
+ <family lang="und-Arab" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoNaskhArabic">
+ NotoNaskhArabic-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font>
+ </family>
+ <family lang="und-Arab" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI">
+ NotoNaskhArabicUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Ethi" varFamilyType="1">
+ <font postScriptName="NotoSansEthiopic-Regular">
+ NotoSansEthiopic-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifEthiopic-Regular">
+ NotoSerifEthiopic-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Hebr">
+ <font weight="400" style="normal" postScriptName="NotoSansHebrew">
+ NotoSansHebrew-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifHebrew-Regular.ttf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifHebrew-Bold.ttf</font>
+ </family>
+ <family lang="und-Thai" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">
+ NotoSerifThai-Regular.ttf
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font>
+ </family>
+ <family lang="und-Thai" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansThaiUI">
+ NotoSansThaiUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Armn" varFamilyType="1">
+ <font postScriptName="NotoSansArmenian-Regular">
+ NotoSansArmenian-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifArmenian-Regular">
+ NotoSerifArmenian-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Geor,und-Geok" varFamilyType="1">
+ <font postScriptName="NotoSansGeorgian-Regular">
+ NotoSansGeorgian-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifGeorgian-Regular">
+ NotoSerifGeorgian-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Deva" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansDevanagari-Regular">
+ NotoSansDevanagari-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifDevanagari-Regular">
+ NotoSerifDevanagari-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Deva" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansDevanagariUI-Regular">
+ NotoSansDevanagariUI-VF.ttf
+ </font>
+ </family>
+
+ <!-- All scripts of India should come after Devanagari, due to shared
+ danda characters.
+ -->
+ <family lang="und-Gujr" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansGujarati">
+ NotoSansGujarati-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Gujr" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansGujaratiUI">
+ NotoSansGujaratiUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Guru" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansGurmukhi-Regular">
+ NotoSansGurmukhi-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifGurmukhi-Regular">
+ NotoSerifGurmukhi-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Guru" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansGurmukhiUI-Regular">
+ NotoSansGurmukhiUI-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Taml" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansTamil-Regular">
+ NotoSansTamil-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifTamil-Regular">
+ NotoSerifTamil-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Taml" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansTamilUI-Regular">
+ NotoSansTamilUI-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Mlym" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansMalayalam-Regular">
+ NotoSansMalayalam-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifMalayalam-Regular">
+ NotoSerifMalayalam-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Mlym" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansMalayalamUI-Regular">
+ NotoSansMalayalamUI-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Beng" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansBengali-Regular">
+ NotoSansBengali-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifBengali-Regular">
+ NotoSerifBengali-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Beng" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansBengaliUI-Regular">
+ NotoSansBengaliUI-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Telu" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansTelugu-Regular">
+ NotoSansTelugu-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifTelugu-Regular">
+ NotoSerifTelugu-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Telu" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansTeluguUI-Regular">
+ NotoSansTeluguUI-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Knda" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansKannada-Regular">
+ NotoSansKannada-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifKannada-Regular">
+ NotoSerifKannada-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Knda" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansKannadaUI-Regular">
+ NotoSansKannadaUI-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Orya" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansOriya">NotoSansOriya-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansOriya-Bold.ttf</font>
+ </family>
+ <family lang="und-Orya" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansOriyaUI">
+ NotoSansOriyaUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Sinh" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansSinhala-Regular">
+ NotoSansSinhala-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifSinhala-Regular">
+ NotoSerifSinhala-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Sinh" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansSinhalaUI-Regular">
+ NotoSansSinhalaUI-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Khmr" variant="elegant">
+ <font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="26.0"/>
+ </font>
+ <font weight="200" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="39.0"/>
+ </font>
+ <font weight="300" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="58.0"/>
+ </font>
+ <font weight="400" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="90.0"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="108.0"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="128.0"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="151.0"/>
+ </font>
+ <font weight="800" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="169.0"/>
+ </font>
+ <font weight="900" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="190.0"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifKhmer-Regular.otf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifKhmer-Bold.otf</font>
+ </family>
+ <family lang="und-Khmr" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansKhmerUI">
+ NotoSansKhmerUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Laoo" variant="elegant">
+ <font weight="400" style="normal">NotoSansLao-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">
+ NotoSerifLao-Regular.ttf
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
+ </family>
+ <family lang="und-Laoo" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Mymr" variant="elegant">
+ <font weight="400" style="normal">NotoSansMyanmar-Regular.otf</font>
+ <font weight="500" style="normal">NotoSansMyanmar-Medium.otf</font>
+ <font weight="700" style="normal">NotoSansMyanmar-Bold.otf</font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifMyanmar-Regular.otf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifMyanmar-Bold.otf</font>
+ </family>
+ <family lang="und-Mymr" variant="compact">
+ <font weight="400" style="normal">NotoSansMyanmarUI-Regular.otf</font>
+ <font weight="500" style="normal">NotoSansMyanmarUI-Medium.otf</font>
+ <font weight="700" style="normal">NotoSansMyanmarUI-Bold.otf</font>
+ </family>
+ <family lang="und-Thaa">
+ <font weight="400" style="normal" postScriptName="NotoSansThaana">
+ NotoSansThaana-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font>
+ </family>
+ <family lang="und-Cham">
+ <font weight="400" style="normal" postScriptName="NotoSansCham">NotoSansCham-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansCham-Bold.ttf</font>
+ </family>
+ <family lang="und-Ahom">
+ <font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
+ </family>
+ <family lang="und-Adlm" varFamilyType="1">
+ <font postScriptName="NotoSansAdlam-Regular">
+ NotoSansAdlam-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Avst">
+ <font weight="400" style="normal" postScriptName="NotoSansAvestan">
+ NotoSansAvestan-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Bali">
+ <font weight="400" style="normal" postScriptName="NotoSansBalinese">
+ NotoSansBalinese-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Bamu">
+ <font weight="400" style="normal" postScriptName="NotoSansBamum">NotoSansBamum-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Batk">
+ <font weight="400" style="normal" postScriptName="NotoSansBatak">NotoSansBatak-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Brah">
+ <font weight="400" style="normal" postScriptName="NotoSansBrahmi">
+ NotoSansBrahmi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Bugi">
+ <font weight="400" style="normal" postScriptName="NotoSansBuginese">
+ NotoSansBuginese-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Buhd">
+ <font weight="400" style="normal" postScriptName="NotoSansBuhid">NotoSansBuhid-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cans">
+ <font weight="400" style="normal">
+ NotoSansCanadianAboriginal-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cari">
+ <font weight="400" style="normal" postScriptName="NotoSansCarian">
+ NotoSansCarian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cakm">
+ <font weight="400" style="normal">NotoSansChakma-Regular.otf</font>
+ </family>
+ <family lang="und-Cher">
+ <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
+ </family>
+ <family lang="und-Copt">
+ <font weight="400" style="normal" postScriptName="NotoSansCoptic">
+ NotoSansCoptic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Xsux">
+ <font weight="400" style="normal" postScriptName="NotoSansCuneiform">
+ NotoSansCuneiform-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cprt">
+ <font weight="400" style="normal" postScriptName="NotoSansCypriot">
+ NotoSansCypriot-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Dsrt">
+ <font weight="400" style="normal" postScriptName="NotoSansDeseret">
+ NotoSansDeseret-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Egyp">
+ <font weight="400" style="normal" postScriptName="NotoSansEgyptianHieroglyphs">
+ NotoSansEgyptianHieroglyphs-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Elba">
+ <font weight="400" style="normal">NotoSansElbasan-Regular.otf</font>
+ </family>
+ <family lang="und-Glag">
+ <font weight="400" style="normal" postScriptName="NotoSansGlagolitic">
+ NotoSansGlagolitic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Goth">
+ <font weight="400" style="normal" postScriptName="NotoSansGothic">
+ NotoSansGothic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Hano">
+ <font weight="400" style="normal" postScriptName="NotoSansHanunoo">
+ NotoSansHanunoo-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Armi">
+ <font weight="400" style="normal" postScriptName="NotoSansImperialAramaic">
+ NotoSansImperialAramaic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Phli">
+ <font weight="400" style="normal" postScriptName="NotoSansInscriptionalPahlavi">
+ NotoSansInscriptionalPahlavi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Prti">
+ <font weight="400" style="normal" postScriptName="NotoSansInscriptionalParthian">
+ NotoSansInscriptionalParthian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Java">
+ <font weight="400" style="normal">NotoSansJavanese-Regular.otf</font>
+ </family>
+ <family lang="und-Kthi">
+ <font weight="400" style="normal" postScriptName="NotoSansKaithi">
+ NotoSansKaithi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Kali">
+ <font weight="400" style="normal" postScriptName="NotoSansKayahLi">
+ NotoSansKayahLi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Khar">
+ <font weight="400" style="normal" postScriptName="NotoSansKharoshthi">
+ NotoSansKharoshthi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lepc">
+ <font weight="400" style="normal" postScriptName="NotoSansLepcha">
+ NotoSansLepcha-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Limb">
+ <font weight="400" style="normal" postScriptName="NotoSansLimbu">NotoSansLimbu-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Linb">
+ <font weight="400" style="normal" postScriptName="NotoSansLinearB">
+ NotoSansLinearB-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lisu">
+ <font weight="400" style="normal" postScriptName="NotoSansLisu">NotoSansLisu-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lyci">
+ <font weight="400" style="normal" postScriptName="NotoSansLycian">
+ NotoSansLycian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lydi">
+ <font weight="400" style="normal" postScriptName="NotoSansLydian">
+ NotoSansLydian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Mand">
+ <font weight="400" style="normal" postScriptName="NotoSansMandaic">
+ NotoSansMandaic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Mtei">
+ <font weight="400" style="normal" postScriptName="NotoSansMeeteiMayek">
+ NotoSansMeeteiMayek-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Talu">
+ <font weight="400" style="normal" postScriptName="NotoSansNewTaiLue">
+ NotoSansNewTaiLue-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Nkoo">
+ <font weight="400" style="normal" postScriptName="NotoSansNKo">NotoSansNKo-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Ogam">
+ <font weight="400" style="normal" postScriptName="NotoSansOgham">NotoSansOgham-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Olck">
+ <font weight="400" style="normal" postScriptName="NotoSansOlChiki">
+ NotoSansOlChiki-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Ital">
+ <font weight="400" style="normal" postScriptName="NotoSansOldItalic">
+ NotoSansOldItalic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Xpeo">
+ <font weight="400" style="normal" postScriptName="NotoSansOldPersian">
+ NotoSansOldPersian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Sarb">
+ <font weight="400" style="normal" postScriptName="NotoSansOldSouthArabian">
+ NotoSansOldSouthArabian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Orkh">
+ <font weight="400" style="normal" postScriptName="NotoSansOldTurkic">
+ NotoSansOldTurkic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Osge">
+ <font weight="400" style="normal">NotoSansOsage-Regular.ttf</font>
+ </family>
+ <family lang="und-Osma">
+ <font weight="400" style="normal" postScriptName="NotoSansOsmanya">
+ NotoSansOsmanya-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Phnx">
+ <font weight="400" style="normal" postScriptName="NotoSansPhoenician">
+ NotoSansPhoenician-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Rjng">
+ <font weight="400" style="normal" postScriptName="NotoSansRejang">
+ NotoSansRejang-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Runr">
+ <font weight="400" style="normal" postScriptName="NotoSansRunic">NotoSansRunic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Samr">
+ <font weight="400" style="normal" postScriptName="NotoSansSamaritan">
+ NotoSansSamaritan-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Saur">
+ <font weight="400" style="normal" postScriptName="NotoSansSaurashtra">
+ NotoSansSaurashtra-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Shaw">
+ <font weight="400" style="normal" postScriptName="NotoSansShavian">
+ NotoSansShavian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Sund">
+ <font weight="400" style="normal" postScriptName="NotoSansSundanese">
+ NotoSansSundanese-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Sylo">
+ <font weight="400" style="normal" postScriptName="NotoSansSylotiNagri">
+ NotoSansSylotiNagri-Regular.ttf
+ </font>
+ </family>
+ <!-- Esrangela should precede Eastern and Western Syriac, since it's our default form. -->
+ <family lang="und-Syre">
+ <font weight="400" style="normal" postScriptName="NotoSansSyriacEstrangela">
+ NotoSansSyriacEstrangela-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Syrn">
+ <font weight="400" style="normal" postScriptName="NotoSansSyriacEastern">
+ NotoSansSyriacEastern-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Syrj">
+ <font weight="400" style="normal" postScriptName="NotoSansSyriacWestern">
+ NotoSansSyriacWestern-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tglg">
+ <font weight="400" style="normal" postScriptName="NotoSansTagalog">
+ NotoSansTagalog-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tagb">
+ <font weight="400" style="normal" postScriptName="NotoSansTagbanwa">
+ NotoSansTagbanwa-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lana">
+ <font weight="400" style="normal" postScriptName="NotoSansTaiTham">
+ NotoSansTaiTham-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tavt">
+ <font weight="400" style="normal" postScriptName="NotoSansTaiViet">
+ NotoSansTaiViet-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tibt" varFamilyType="1">
+ <font postScriptName="NotoSerifTibetan-Regular">
+ NotoSerifTibetan-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Tfng">
+ <font weight="400" style="normal">NotoSansTifinagh-Regular.otf</font>
+ </family>
+ <family lang="und-Ugar">
+ <font weight="400" style="normal" postScriptName="NotoSansUgaritic">
+ NotoSansUgaritic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Vaii">
+ <font weight="400" style="normal" postScriptName="NotoSansVai">NotoSansVai-Regular.ttf
+ </font>
+ </family>
+ <family>
+ <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
+ </family>
+ <family lang="zh-Hans">
+ <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
+ </font>
+ <font weight="400" style="normal" index="2" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="zh-Hant,zh-Bopo">
+ <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
+ </font>
+ <font weight="400" style="normal" index="3" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="ja">
+ <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
+ </font>
+ <font weight="400" style="normal" index="0" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="ko">
+ <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
+ </font>
+ <font weight="400" style="normal" index="1" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="und-Zsye">
+ <font weight="400" style="normal">NotoColorEmoji.ttf</font>
+ </family>
+ <family lang="und-Zsye">
+ <font weight="400" style="normal">NotoColorEmojiFlags.ttf</font>
+ </family>
+ <family lang="und-Zsym">
+ <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted2.ttf</font>
+ </family>
+ <!--
+ Tai Le, Yi, Mongolian, and Phags-pa are intentionally kept last, to make sure they don't
+ override the East Asian punctuation for Chinese.
+ -->
+ <family lang="und-Tale">
+ <font weight="400" style="normal" postScriptName="NotoSansTaiLe">NotoSansTaiLe-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Yiii">
+ <font weight="400" style="normal" postScriptName="NotoSansYi">NotoSansYi-Regular.ttf</font>
+ </family>
+ <family lang="und-Mong">
+ <font weight="400" style="normal" postScriptName="NotoSansMongolian">
+ NotoSansMongolian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Phag">
+ <font weight="400" style="normal" postScriptName="NotoSansPhagsPa">
+ NotoSansPhagsPa-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Hluw">
+ <font weight="400" style="normal">NotoSansAnatolianHieroglyphs-Regular.otf</font>
+ </family>
+ <family lang="und-Bass">
+ <font weight="400" style="normal">NotoSansBassaVah-Regular.otf</font>
+ </family>
+ <family lang="und-Bhks">
+ <font weight="400" style="normal">NotoSansBhaiksuki-Regular.otf</font>
+ </family>
+ <family lang="und-Hatr">
+ <font weight="400" style="normal">NotoSansHatran-Regular.otf</font>
+ </family>
+ <family lang="und-Lina">
+ <font weight="400" style="normal">NotoSansLinearA-Regular.otf</font>
+ </family>
+ <family lang="und-Mani">
+ <font weight="400" style="normal">NotoSansManichaean-Regular.otf</font>
+ </family>
+ <family lang="und-Marc">
+ <font weight="400" style="normal">NotoSansMarchen-Regular.otf</font>
+ </family>
+ <family lang="und-Merc">
+ <font weight="400" style="normal">NotoSansMeroitic-Regular.otf</font>
+ </family>
+ <family lang="und-Plrd">
+ <font weight="400" style="normal">NotoSansMiao-Regular.otf</font>
+ </family>
+ <family lang="und-Mroo">
+ <font weight="400" style="normal">NotoSansMro-Regular.otf</font>
+ </family>
+ <family lang="und-Mult">
+ <font weight="400" style="normal">NotoSansMultani-Regular.otf</font>
+ </family>
+ <family lang="und-Nbat">
+ <font weight="400" style="normal">NotoSansNabataean-Regular.otf</font>
+ </family>
+ <family lang="und-Newa">
+ <font weight="400" style="normal">NotoSansNewa-Regular.otf</font>
+ </family>
+ <family lang="und-Narb">
+ <font weight="400" style="normal">NotoSansOldNorthArabian-Regular.otf</font>
+ </family>
+ <family lang="und-Perm">
+ <font weight="400" style="normal">NotoSansOldPermic-Regular.otf</font>
+ </family>
+ <family lang="und-Hmng">
+ <font weight="400" style="normal">NotoSansPahawhHmong-Regular.otf</font>
+ </family>
+ <family lang="und-Palm">
+ <font weight="400" style="normal">NotoSansPalmyrene-Regular.otf</font>
+ </family>
+ <family lang="und-Pauc">
+ <font weight="400" style="normal">NotoSansPauCinHau-Regular.otf</font>
+ </family>
+ <family lang="und-Shrd">
+ <font weight="400" style="normal">NotoSansSharada-Regular.otf</font>
+ </family>
+ <family lang="und-Sora">
+ <font weight="400" style="normal">NotoSansSoraSompeng-Regular.otf</font>
+ </family>
+ <family lang="und-Gong">
+ <font weight="400" style="normal">NotoSansGunjalaGondi-Regular.otf</font>
+ </family>
+ <family lang="und-Rohg">
+ <font weight="400" style="normal">NotoSansHanifiRohingya-Regular.otf</font>
+ </family>
+ <family lang="und-Khoj">
+ <font weight="400" style="normal">NotoSansKhojki-Regular.otf</font>
+ </family>
+ <family lang="und-Gonm">
+ <font weight="400" style="normal">NotoSansMasaramGondi-Regular.otf</font>
+ </family>
+ <family lang="und-Wcho">
+ <font weight="400" style="normal">NotoSansWancho-Regular.otf</font>
+ </family>
+ <family lang="und-Wara">
+ <font weight="400" style="normal">NotoSansWarangCiti-Regular.otf</font>
+ </family>
+ <family lang="und-Gran">
+ <font weight="400" style="normal">NotoSansGrantha-Regular.ttf</font>
+ </family>
+ <family lang="und-Modi">
+ <font weight="400" style="normal">NotoSansModi-Regular.ttf</font>
+ </family>
+ <family lang="und-Dogr">
+ <font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font>
+ </family>
+ <family lang="und-Medf" varFamilyType="1">
+ <font postScriptName="NotoSansMedefaidrin-Regular">
+ NotoSansMedefaidrin-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Soyo" varFamilyType="1">
+ <font postScriptName="NotoSansSoyombo-Regular">
+ NotoSansSoyombo-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Takr" varFamilyType="1">
+ <font postScriptName="NotoSansTakri-Regular">
+ NotoSansTakri-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Hmnp" varFamilyType="1">
+ <font postScriptName="NotoSerifHmongNyiakeng-Regular">
+ NotoSerifNyiakengPuachueHmong-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Yezi" varFamilyType="1">
+ <font postScriptName="NotoSerifYezidi-Regular">
+ NotoSerifYezidi-VF.ttf
+ </font>
+ </family>
+</familyset>
diff --git a/data/fonts/fonts_cjkvf.xml b/data/fonts/fonts_cjkvf.xml
new file mode 100644
index 0000000..8d91edd
--- /dev/null
+++ b/data/fonts/fonts_cjkvf.xml
@@ -0,0 +1,1785 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ DEPRECATED: This XML file is no longer a source of the font files installed
+ in the system.
+
+ For the device vendors: please add your font configurations to the
+ platform/frameworks/base/data/font_fallback.xml and also add it to this XML
+ file as much as possible for apps that reads this XML file.
+
+ For the application developers: please stop reading this XML file and use
+ android.graphics.fonts.SystemFonts#getAvailableFonts Java API or
+ ASystemFontIterator_open NDK API for getting list of system installed
+ font files.
+
+ WARNING: Parsing of this file by third-party apps is not supported. The
+ file, and the font files it refers to, will be renamed and/or moved out
+ from their respective location in the next Android release, and/or the
+ format or syntax of the file may change significantly. If you parse this
+ file for information about system fonts, do it at your own risk. Your
+ application will almost certainly break with the next major Android
+ release.
+
+ In this file, all fonts without names are added to the default list.
+ Fonts are chosen based on a match: full BCP-47 language tag including
+ script, then just language, and finally order (the first font containing
+ the glyph).
+
+ Order of appearance is also the tiebreaker for weight matching. This is
+ the reason why the 900 weights of Roboto precede the 700 weights - we
+ prefer the former when an 800 weight is requested. Since bold spans
+ effectively add 300 to the weight, this ensures that 900 is the bold
+ paired with the 500 weight, ensuring adequate contrast.
+
+ TODO(rsheeter) update comment; ordering to match 800 to 900 is no longer required
+-->
+<familyset version="23">
+ <!-- first font is default -->
+ <family name="sans-serif">
+ <font weight="100" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <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>
+
+
+ <!-- Note that aliases must come after the fonts they reference. -->
+ <alias name="sans-serif-thin" to="sans-serif" weight="100" />
+ <alias name="sans-serif-light" to="sans-serif" weight="300" />
+ <alias name="sans-serif-medium" to="sans-serif" weight="500" />
+ <alias name="sans-serif-black" to="sans-serif" weight="900" />
+ <alias name="arial" to="sans-serif" />
+ <alias name="helvetica" to="sans-serif" />
+ <alias name="tahoma" to="sans-serif" />
+ <alias name="verdana" to="sans-serif" />
+
+ <family name="sans-serif-condensed">
+ <font weight="100" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="100" />
+ </font>
+ <font weight="200" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="200" />
+ </font>
+ <font weight="300" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="300" />
+ </font>
+ <font weight="400" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="800" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="800" />
+ </font>
+ <font weight="900" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="900" />
+ </font>
+ <font weight="100" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="100" />
+ </font>
+ <font weight="200" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="200" />
+ </font>
+ <font weight="300" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="300" />
+ </font>
+ <font weight="400" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="800" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="800" />
+ </font>
+ <font weight="900" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="900" />
+ </font>
+ </family>
+ <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" />
+ <alias name="sans-serif-condensed-medium" to="sans-serif-condensed" weight="500" />
+
+ <family name="serif">
+ <font weight="400" style="normal" postScriptName="NotoSerif">NotoSerif-Regular.ttf</font>
+ <font weight="700" style="normal">NotoSerif-Bold.ttf</font>
+ <font weight="400" style="italic">NotoSerif-Italic.ttf</font>
+ <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font>
+ </family>
+ <alias name="serif-bold" to="serif" weight="700" />
+ <alias name="times" to="serif" />
+ <alias name="times new roman" to="serif" />
+ <alias name="palatino" to="serif" />
+ <alias name="georgia" to="serif" />
+ <alias name="baskerville" to="serif" />
+ <alias name="goudy" to="serif" />
+ <alias name="fantasy" to="serif" />
+ <alias name="ITC Stone Serif" to="serif" />
+
+ <family name="monospace">
+ <font weight="400" style="normal">DroidSansMono.ttf</font>
+ </family>
+ <alias name="sans-serif-monospace" to="monospace" />
+ <alias name="monaco" to="monospace" />
+
+ <family name="serif-monospace">
+ <font weight="400" style="normal" postScriptName="CutiveMono-Regular">CutiveMono.ttf</font>
+ </family>
+ <alias name="courier" to="serif-monospace" />
+ <alias name="courier new" to="serif-monospace" />
+
+ <family name="casual">
+ <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>
+
+ <family name="sans-serif-smallcaps">
+ <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
+ </family>
+
+ <family name="source-sans-pro">
+ <font weight="400" style="normal">SourceSansPro-Regular.ttf</font>
+ <font weight="400" style="italic">SourceSansPro-Italic.ttf</font>
+ <font weight="600" style="normal">SourceSansPro-SemiBold.ttf</font>
+ <font weight="600" style="italic">SourceSansPro-SemiBoldItalic.ttf</font>
+ <font weight="700" style="normal">SourceSansPro-Bold.ttf</font>
+ <font weight="700" style="italic">SourceSansPro-BoldItalic.ttf</font>
+ </family>
+ <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
+
+ <family name="roboto-flex">
+ <font weight="100" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="100" />
+ </font>
+ <font weight="200" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="200" />
+ </font>
+ <font weight="300" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="300" />
+ </font>
+ <font weight="400" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="800" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="800" />
+ </font>
+ <font weight="900" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="900" />
+ </font>
+ <font weight="100" style="italic">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="-10" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="100" />
+ </font>
+ <font weight="200" style="italic">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="-10" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="200" />
+ </font>
+ <font weight="300" style="italic">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="-10" />
+ <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>
+
+ <!-- fallback fonts -->
+ <family lang="und-Arab" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoNaskhArabic">
+ NotoNaskhArabic-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font>
+ </family>
+ <family lang="und-Arab" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI">
+ NotoNaskhArabicUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Ethi">
+ <font weight="400" style="normal" postScriptName="NotoSansEthiopic-Regular">
+ NotoSansEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansEthiopic-Regular">
+ 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>
+ </family>
+ <family lang="und-Hebr">
+ <font weight="400" style="normal" postScriptName="NotoSansHebrew">
+ NotoSansHebrew-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifHebrew-Regular.ttf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifHebrew-Bold.ttf</font>
+ </family>
+ <family lang="und-Thai" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">
+ NotoSerifThai-Regular.ttf
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font>
+ </family>
+ <family lang="und-Thai" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansThaiUI">
+ NotoSansThaiUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Armn">
+ <font weight="400" style="normal" postScriptName="NotoSansArmenian-Regular">
+ NotoSansArmenian-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansArmenian-Regular">
+ NotoSansArmenian-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansArmenian-Regular">
+ NotoSansArmenian-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansArmenian-Regular">
+ 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>
+ </family>
+ <family lang="und-Geor,und-Geok">
+ <font weight="400" style="normal" 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>
+ </family>
+ <family lang="und-Deva" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansDevanagari-Regular">
+ NotoSansDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansDevanagari-Regular">
+ NotoSansDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansDevanagari-Regular">
+ 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>
+ </family>
+ <family lang="und-Deva" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
+ NotoSansDevanagariUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
+ 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>
+
+ <!-- All scripts of India should come after Devanagari, due to shared
+ danda characters.
+ -->
+ <family lang="und-Gujr" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansGujarati">
+ NotoSansGujarati-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Gujr" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansGujaratiUI">
+ NotoSansGujaratiUI-Regular.ttf
+ </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">
+ 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>
+ </family>
+ <family lang="und-Guru" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+ NotoSansGurmukhiUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+ NotoSansGurmukhiUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+ NotoSansGurmukhiUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+ NotoSansGurmukhiUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Taml" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansTamil-Regular">
+ NotoSansTamil-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansTamil-Regular">
+ 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>
+ </family>
+ <family lang="und-Taml" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansTamilUI-Regular">
+ NotoSansTamilUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansTamilUI-Regular">
+ NotoSansTamilUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansTamilUI-Regular">
+ NotoSansTamilUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansTamilUI-Regular">
+ NotoSansTamilUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Mlym" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansMalayalam-Regular">
+ NotoSansMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansMalayalam-Regular">
+ 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>
+ </family>
+ <family lang="und-Mlym" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+ NotoSansMalayalamUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+ NotoSansMalayalamUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+ NotoSansMalayalamUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+ NotoSansMalayalamUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Beng" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansBengali-Regular">
+ NotoSansBengali-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansBengali-Regular">
+ 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>
+ </family>
+ <family lang="und-Beng" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+ NotoSansBengaliUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+ NotoSansBengaliUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+ NotoSansBengaliUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+ NotoSansBengaliUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Telu" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansTelugu-Regular">
+ NotoSansTelugu-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansTelugu-Regular">
+ 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>
+ </family>
+ <family lang="und-Telu" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansTeluguUI-Regular">
+ NotoSansTeluguUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansTeluguUI-Regular">
+ NotoSansTeluguUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansTeluguUI-Regular">
+ 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">
+ 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>
+ </family>
+ <family lang="und-Knda" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansKannadaUI-Regular">
+ NotoSansKannadaUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansKannadaUI-Regular">
+ 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">
+ <font weight="400" style="normal" postScriptName="NotoSansOriya">NotoSansOriya-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansOriya-Bold.ttf</font>
+ </family>
+ <family lang="und-Orya" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansOriyaUI">
+ NotoSansOriyaUI-Regular.ttf
+ </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">
+ 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>
+ </family>
+ <family lang="und-Sinh" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+ NotoSansSinhalaUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+ NotoSansSinhalaUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+ NotoSansSinhalaUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+ NotoSansSinhalaUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Khmr" variant="elegant">
+ <font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="26.0"/>
+ </font>
+ <font weight="200" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="39.0"/>
+ </font>
+ <font weight="300" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="58.0"/>
+ </font>
+ <font weight="400" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="90.0"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="108.0"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="128.0"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="151.0"/>
+ </font>
+ <font weight="800" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="169.0"/>
+ </font>
+ <font weight="900" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="190.0"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifKhmer-Regular.otf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifKhmer-Bold.otf</font>
+ </family>
+ <family lang="und-Khmr" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansKhmerUI">
+ NotoSansKhmerUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Laoo" variant="elegant">
+ <font weight="400" style="normal">NotoSansLao-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">
+ NotoSerifLao-Regular.ttf
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
+ </family>
+ <family lang="und-Laoo" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Mymr" variant="elegant">
+ <font weight="400" style="normal">NotoSansMyanmar-Regular.otf</font>
+ <font weight="500" style="normal">NotoSansMyanmar-Medium.otf</font>
+ <font weight="700" style="normal">NotoSansMyanmar-Bold.otf</font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifMyanmar-Regular.otf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifMyanmar-Bold.otf</font>
+ </family>
+ <family lang="und-Mymr" variant="compact">
+ <font weight="400" style="normal">NotoSansMyanmarUI-Regular.otf</font>
+ <font weight="500" style="normal">NotoSansMyanmarUI-Medium.otf</font>
+ <font weight="700" style="normal">NotoSansMyanmarUI-Bold.otf</font>
+ </family>
+ <family lang="und-Thaa">
+ <font weight="400" style="normal" postScriptName="NotoSansThaana">
+ NotoSansThaana-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font>
+ </family>
+ <family lang="und-Cham">
+ <font weight="400" style="normal" postScriptName="NotoSansCham">NotoSansCham-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansCham-Bold.ttf</font>
+ </family>
+ <family lang="und-Ahom">
+ <font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
+ </family>
+ <family lang="und-Adlm">
+ <font weight="400" style="normal" postScriptName="NotoSansAdlam-Regular">
+ NotoSansAdlam-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansAdlam-Regular">
+ 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">
+ <font weight="400" style="normal" postScriptName="NotoSansAvestan">
+ NotoSansAvestan-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Bali">
+ <font weight="400" style="normal" postScriptName="NotoSansBalinese">
+ NotoSansBalinese-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Bamu">
+ <font weight="400" style="normal" postScriptName="NotoSansBamum">NotoSansBamum-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Batk">
+ <font weight="400" style="normal" postScriptName="NotoSansBatak">NotoSansBatak-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Brah">
+ <font weight="400" style="normal" postScriptName="NotoSansBrahmi">
+ NotoSansBrahmi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Bugi">
+ <font weight="400" style="normal" postScriptName="NotoSansBuginese">
+ NotoSansBuginese-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Buhd">
+ <font weight="400" style="normal" postScriptName="NotoSansBuhid">NotoSansBuhid-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cans">
+ <font weight="400" style="normal">
+ NotoSansCanadianAboriginal-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cari">
+ <font weight="400" style="normal" postScriptName="NotoSansCarian">
+ NotoSansCarian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cakm">
+ <font weight="400" style="normal">NotoSansChakma-Regular.otf</font>
+ </family>
+ <family lang="und-Cher">
+ <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
+ </family>
+ <family lang="und-Copt">
+ <font weight="400" style="normal" postScriptName="NotoSansCoptic">
+ NotoSansCoptic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Xsux">
+ <font weight="400" style="normal" postScriptName="NotoSansCuneiform">
+ NotoSansCuneiform-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cprt">
+ <font weight="400" style="normal" postScriptName="NotoSansCypriot">
+ NotoSansCypriot-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Dsrt">
+ <font weight="400" style="normal" postScriptName="NotoSansDeseret">
+ NotoSansDeseret-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Egyp">
+ <font weight="400" style="normal" postScriptName="NotoSansEgyptianHieroglyphs">
+ NotoSansEgyptianHieroglyphs-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Elba">
+ <font weight="400" style="normal">NotoSansElbasan-Regular.otf</font>
+ </family>
+ <family lang="und-Glag">
+ <font weight="400" style="normal" postScriptName="NotoSansGlagolitic">
+ NotoSansGlagolitic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Goth">
+ <font weight="400" style="normal" postScriptName="NotoSansGothic">
+ NotoSansGothic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Hano">
+ <font weight="400" style="normal" postScriptName="NotoSansHanunoo">
+ NotoSansHanunoo-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Armi">
+ <font weight="400" style="normal" postScriptName="NotoSansImperialAramaic">
+ NotoSansImperialAramaic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Phli">
+ <font weight="400" style="normal" postScriptName="NotoSansInscriptionalPahlavi">
+ NotoSansInscriptionalPahlavi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Prti">
+ <font weight="400" style="normal" postScriptName="NotoSansInscriptionalParthian">
+ NotoSansInscriptionalParthian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Java">
+ <font weight="400" style="normal">NotoSansJavanese-Regular.otf</font>
+ </family>
+ <family lang="und-Kthi">
+ <font weight="400" style="normal" postScriptName="NotoSansKaithi">
+ NotoSansKaithi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Kali">
+ <font weight="400" style="normal" postScriptName="NotoSansKayahLi">
+ NotoSansKayahLi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Khar">
+ <font weight="400" style="normal" postScriptName="NotoSansKharoshthi">
+ NotoSansKharoshthi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lepc">
+ <font weight="400" style="normal" postScriptName="NotoSansLepcha">
+ NotoSansLepcha-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Limb">
+ <font weight="400" style="normal" postScriptName="NotoSansLimbu">NotoSansLimbu-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Linb">
+ <font weight="400" style="normal" postScriptName="NotoSansLinearB">
+ NotoSansLinearB-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lisu">
+ <font weight="400" style="normal" postScriptName="NotoSansLisu">NotoSansLisu-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lyci">
+ <font weight="400" style="normal" postScriptName="NotoSansLycian">
+ NotoSansLycian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lydi">
+ <font weight="400" style="normal" postScriptName="NotoSansLydian">
+ NotoSansLydian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Mand">
+ <font weight="400" style="normal" postScriptName="NotoSansMandaic">
+ NotoSansMandaic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Mtei">
+ <font weight="400" style="normal" postScriptName="NotoSansMeeteiMayek">
+ NotoSansMeeteiMayek-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Talu">
+ <font weight="400" style="normal" postScriptName="NotoSansNewTaiLue">
+ NotoSansNewTaiLue-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Nkoo">
+ <font weight="400" style="normal" postScriptName="NotoSansNKo">NotoSansNKo-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Ogam">
+ <font weight="400" style="normal" postScriptName="NotoSansOgham">NotoSansOgham-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Olck">
+ <font weight="400" style="normal" postScriptName="NotoSansOlChiki">
+ NotoSansOlChiki-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Ital">
+ <font weight="400" style="normal" postScriptName="NotoSansOldItalic">
+ NotoSansOldItalic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Xpeo">
+ <font weight="400" style="normal" postScriptName="NotoSansOldPersian">
+ NotoSansOldPersian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Sarb">
+ <font weight="400" style="normal" postScriptName="NotoSansOldSouthArabian">
+ NotoSansOldSouthArabian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Orkh">
+ <font weight="400" style="normal" postScriptName="NotoSansOldTurkic">
+ NotoSansOldTurkic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Osge">
+ <font weight="400" style="normal">NotoSansOsage-Regular.ttf</font>
+ </family>
+ <family lang="und-Osma">
+ <font weight="400" style="normal" postScriptName="NotoSansOsmanya">
+ NotoSansOsmanya-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Phnx">
+ <font weight="400" style="normal" postScriptName="NotoSansPhoenician">
+ NotoSansPhoenician-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Rjng">
+ <font weight="400" style="normal" postScriptName="NotoSansRejang">
+ NotoSansRejang-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Runr">
+ <font weight="400" style="normal" postScriptName="NotoSansRunic">NotoSansRunic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Samr">
+ <font weight="400" style="normal" postScriptName="NotoSansSamaritan">
+ NotoSansSamaritan-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Saur">
+ <font weight="400" style="normal" postScriptName="NotoSansSaurashtra">
+ NotoSansSaurashtra-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Shaw">
+ <font weight="400" style="normal" postScriptName="NotoSansShavian">
+ NotoSansShavian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Sund">
+ <font weight="400" style="normal" postScriptName="NotoSansSundanese">
+ NotoSansSundanese-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Sylo">
+ <font weight="400" style="normal" postScriptName="NotoSansSylotiNagri">
+ NotoSansSylotiNagri-Regular.ttf
+ </font>
+ </family>
+ <!-- Esrangela should precede Eastern and Western Syriac, since it's our default form. -->
+ <family lang="und-Syre">
+ <font weight="400" style="normal" postScriptName="NotoSansSyriacEstrangela">
+ NotoSansSyriacEstrangela-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Syrn">
+ <font weight="400" style="normal" postScriptName="NotoSansSyriacEastern">
+ NotoSansSyriacEastern-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Syrj">
+ <font weight="400" style="normal" postScriptName="NotoSansSyriacWestern">
+ NotoSansSyriacWestern-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tglg">
+ <font weight="400" style="normal" postScriptName="NotoSansTagalog">
+ NotoSansTagalog-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tagb">
+ <font weight="400" style="normal" postScriptName="NotoSansTagbanwa">
+ NotoSansTagbanwa-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lana">
+ <font weight="400" style="normal" postScriptName="NotoSansTaiTham">
+ NotoSansTaiTham-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tavt">
+ <font weight="400" style="normal" postScriptName="NotoSansTaiViet">
+ NotoSansTaiViet-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tibt">
+ <font weight="400" style="normal" 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">
+ <font weight="400" style="normal">NotoSansTifinagh-Regular.otf</font>
+ </family>
+ <family lang="und-Ugar">
+ <font weight="400" style="normal" postScriptName="NotoSansUgaritic">
+ NotoSansUgaritic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Vaii">
+ <font weight="400" style="normal" postScriptName="NotoSansVai">NotoSansVai-Regular.ttf
+ </font>
+ </family>
+ <family>
+ <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
+ </family>
+ <family lang="zh-Hans">
+ <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
+ </font>
+ <font weight="400" style="normal" index="2" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="zh-Hant,zh-Bopo">
+ <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
+ </font>
+ <font weight="400" style="normal" index="3" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="ja">
+ <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
+ </font>
+ <font weight="400" style="normal" index="0" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="ko">
+ <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
+ </font>
+ <font weight="400" style="normal" index="1" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="und-Zsye" ignore="true">
+ <font weight="400" style="normal">NotoColorEmojiLegacy.ttf</font>
+ </family>
+ <family lang="und-Zsye">
+ <font weight="400" style="normal">NotoColorEmoji.ttf</font>
+ </family>
+ <family lang="und-Zsye">
+ <font weight="400" style="normal">NotoColorEmojiFlags.ttf</font>
+ </family>
+ <family lang="und-Zsym">
+ <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted2.ttf</font>
+ </family>
+ <!--
+ Tai Le, Yi, Mongolian, and Phags-pa are intentionally kept last, to make sure they don't
+ override the East Asian punctuation for Chinese.
+ -->
+ <family lang="und-Tale">
+ <font weight="400" style="normal" postScriptName="NotoSansTaiLe">NotoSansTaiLe-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Yiii">
+ <font weight="400" style="normal" postScriptName="NotoSansYi">NotoSansYi-Regular.ttf</font>
+ </family>
+ <family lang="und-Mong">
+ <font weight="400" style="normal" postScriptName="NotoSansMongolian">
+ NotoSansMongolian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Phag">
+ <font weight="400" style="normal" postScriptName="NotoSansPhagsPa">
+ NotoSansPhagsPa-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Hluw">
+ <font weight="400" style="normal">NotoSansAnatolianHieroglyphs-Regular.otf</font>
+ </family>
+ <family lang="und-Bass">
+ <font weight="400" style="normal">NotoSansBassaVah-Regular.otf</font>
+ </family>
+ <family lang="und-Bhks">
+ <font weight="400" style="normal">NotoSansBhaiksuki-Regular.otf</font>
+ </family>
+ <family lang="und-Hatr">
+ <font weight="400" style="normal">NotoSansHatran-Regular.otf</font>
+ </family>
+ <family lang="und-Lina">
+ <font weight="400" style="normal">NotoSansLinearA-Regular.otf</font>
+ </family>
+ <family lang="und-Mani">
+ <font weight="400" style="normal">NotoSansManichaean-Regular.otf</font>
+ </family>
+ <family lang="und-Marc">
+ <font weight="400" style="normal">NotoSansMarchen-Regular.otf</font>
+ </family>
+ <family lang="und-Merc">
+ <font weight="400" style="normal">NotoSansMeroitic-Regular.otf</font>
+ </family>
+ <family lang="und-Plrd">
+ <font weight="400" style="normal">NotoSansMiao-Regular.otf</font>
+ </family>
+ <family lang="und-Mroo">
+ <font weight="400" style="normal">NotoSansMro-Regular.otf</font>
+ </family>
+ <family lang="und-Mult">
+ <font weight="400" style="normal">NotoSansMultani-Regular.otf</font>
+ </family>
+ <family lang="und-Nbat">
+ <font weight="400" style="normal">NotoSansNabataean-Regular.otf</font>
+ </family>
+ <family lang="und-Newa">
+ <font weight="400" style="normal">NotoSansNewa-Regular.otf</font>
+ </family>
+ <family lang="und-Narb">
+ <font weight="400" style="normal">NotoSansOldNorthArabian-Regular.otf</font>
+ </family>
+ <family lang="und-Perm">
+ <font weight="400" style="normal">NotoSansOldPermic-Regular.otf</font>
+ </family>
+ <family lang="und-Hmng">
+ <font weight="400" style="normal">NotoSansPahawhHmong-Regular.otf</font>
+ </family>
+ <family lang="und-Palm">
+ <font weight="400" style="normal">NotoSansPalmyrene-Regular.otf</font>
+ </family>
+ <family lang="und-Pauc">
+ <font weight="400" style="normal">NotoSansPauCinHau-Regular.otf</font>
+ </family>
+ <family lang="und-Shrd">
+ <font weight="400" style="normal">NotoSansSharada-Regular.otf</font>
+ </family>
+ <family lang="und-Sora">
+ <font weight="400" style="normal">NotoSansSoraSompeng-Regular.otf</font>
+ </family>
+ <family lang="und-Gong">
+ <font weight="400" style="normal">NotoSansGunjalaGondi-Regular.otf</font>
+ </family>
+ <family lang="und-Rohg">
+ <font weight="400" style="normal">NotoSansHanifiRohingya-Regular.otf</font>
+ </family>
+ <family lang="und-Khoj">
+ <font weight="400" style="normal">NotoSansKhojki-Regular.otf</font>
+ </family>
+ <family lang="und-Gonm">
+ <font weight="400" style="normal">NotoSansMasaramGondi-Regular.otf</font>
+ </family>
+ <family lang="und-Wcho">
+ <font weight="400" style="normal">NotoSansWancho-Regular.otf</font>
+ </family>
+ <family lang="und-Wara">
+ <font weight="400" style="normal">NotoSansWarangCiti-Regular.otf</font>
+ </family>
+ <family lang="und-Gran">
+ <font weight="400" style="normal">NotoSansGrantha-Regular.ttf</font>
+ </family>
+ <family lang="und-Modi">
+ <font weight="400" style="normal">NotoSansModi-Regular.ttf</font>
+ </family>
+ <family lang="und-Dogr">
+ <font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font>
+ </family>
+ <family lang="und-Medf">
+ <font weight="400" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+ NotoSansMedefaidrin-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+ NotoSansMedefaidrin-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+ NotoSansMedefaidrin-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+ NotoSansMedefaidrin-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Soyo">
+ <font weight="400" style="normal" postScriptName="NotoSansSoyombo-Regular">
+ NotoSansSoyombo-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansSoyombo-Regular">
+ 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">
+ NotoSansTakri-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansTakri-Regular">
+ NotoSansTakri-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansTakri-Regular">
+ NotoSansTakri-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansTakri-Regular">
+ NotoSansTakri-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Hmnp">
+ <font weight="400" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+ NotoSerifNyiakengPuachueHmong-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+ NotoSerifNyiakengPuachueHmong-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+ NotoSerifNyiakengPuachueHmong-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+ NotoSerifNyiakengPuachueHmong-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Yezi">
+ <font weight="400" style="normal" postScriptName="NotoSerifYezidi-Regular">
+ NotoSerifYezidi-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSerifYezidi-Regular">
+ 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/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index f6ba103..ae61a2d 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -17,6 +17,7 @@
package android.graphics;
import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
+import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
import android.annotation.ColorInt;
import android.annotation.ColorLong;
@@ -133,7 +134,9 @@
FAKE_BOLD_TEXT_FLAG,
LINEAR_TEXT_FLAG,
SUBPIXEL_TEXT_FLAG,
- EMBEDDED_BITMAP_TEXT_FLAG
+ EMBEDDED_BITMAP_TEXT_FLAG,
+ TEXT_RUN_FLAG_LEFT_EDGE,
+ TEXT_RUN_FLAG_RIGHT_EDGE
})
public @interface PaintFlag{}
@@ -264,6 +267,66 @@
/** @hide bit mask for the flag enabling vertical rendering for text */
public static final int VERTICAL_TEXT_FLAG = 0x1000;
+ /**
+ * A text run flag that indicates the run is located the visually most left segment of the line.
+ * <p>
+ * This flag is used for telling the underlying text layout engine that the text is located at
+ * the most left of the line. This flag is used for controlling the amount letter spacing
+ * added. If the text is in the middle of the line, the text layout engine assigns additional
+ * letter spacing to the both side of each letter. On the other hand, the letter spacing should
+ * not be added to the visually most left and right of the line. By setting this flag, text
+ * layout engine calculates the layout as it is located at the most visually left of the line
+ * and doesn't add letter spacing to the left of this run.
+ * <p>
+ * Note that the caller must resolve BiDi runs and reorder them visually and set this flag only
+ * if the target run is located visually most left position. This left does not always mean the
+ * beginning of the text.
+ * <p>
+ * If the run covers entire line, caller should set {@link #TEXT_RUN_FLAG_RIGHT_EDGE} as well.
+ * <p>
+ * Note that this flag is only effective for run based APIs. For example, this flag works for
+ * {@link Canvas#drawTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)}
+ * and
+ * {@link Paint#getRunCharacterAdvance(char[], int, int, int, int, boolean, int, float[], int)}.
+ * However, this doesn't work for
+ * {@link Canvas#drawText(CharSequence, int, int, float, float, Paint)} or
+ * {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both
+ * {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified.
+ */
+ @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ public static final int TEXT_RUN_FLAG_LEFT_EDGE = 0x2000;
+
+
+ /**
+ * A text run flag that indicates the run is located the visually most right segment of the
+ * line.
+ * <p>
+ * This flag is used for telling the underlying text layout engine that the text is located at
+ * the most right of the line. This flag is used for controlling the amount letter spacing
+ * added. If the text is in the middle of the line, the text layout engine assigns additional
+ * letter spacing to the both side of each letter. On the other hand, the letter spacing should
+ * not be added to the visually most left and right of the line. By setting this flag, text
+ * layout engine calculates the layout as it is located at the most visually left of the line
+ * and doesn't add letter spacing to the left of this run.
+ * <p>
+ * Note that the caller must resolve BiDi runs and reorder them visually and set this flag only
+ * if the target run is located visually most right position. This right does not always mean
+ * the end of the text.
+ * <p>
+ * If the run covers entire line, caller should set {@link #TEXT_RUN_FLAG_LEFT_EDGE} as well.
+ * <p>
+ * Note that this flag is only effective for run based APIs. For example, this flag works for
+ * {@link Canvas#drawTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)}
+ * and
+ * {@link Paint#getRunCharacterAdvance(char[], int, int, int, int, boolean, int, float[], int)}.
+ * However, this doesn't work for
+ * {@link Canvas#drawText(CharSequence, int, int, float, float, Paint)} or
+ * {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both
+ * {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified.
+ */
+ @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 0x4000;
+
// These flags are always set on a new/reset paint, even if flags 0 is passed.
static final int HIDDEN_DEFAULT_PAINT_FLAGS = DEV_KERN_TEXT_FLAG | EMBEDDED_BITMAP_TEXT_FLAG
| FILTER_BITMAP_FLAG;
@@ -2520,17 +2583,24 @@
if (text.length == 0 || count == 0) {
return 0f;
}
- if (!mHasCompatScaling) {
- return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
- index, count, index, count, mBidiFlags, null, 0));
- }
+ int oldFlag = getFlags();
+ setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));
+ try {
- final float oldSize = getTextSize();
- setTextSize(oldSize * mCompatScaling);
- final float w = nGetTextAdvances(mNativePaint, text, index, count, index, count,
- mBidiFlags, null, 0);
- setTextSize(oldSize);
- return (float) Math.ceil(w*mInvCompatScaling);
+ if (!mHasCompatScaling) {
+ return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
+ index, count, index, count, mBidiFlags, null, 0));
+ }
+
+ final float oldSize = getTextSize();
+ setTextSize(oldSize * mCompatScaling);
+ final float w = nGetTextAdvances(mNativePaint, text, index, count, index, count,
+ mBidiFlags, null, 0);
+ setTextSize(oldSize);
+ return (float) Math.ceil(w * mInvCompatScaling);
+ } finally {
+ setFlags(oldFlag);
+ }
}
/**
@@ -2552,16 +2622,22 @@
if (text.length() == 0 || start == end) {
return 0f;
}
- if (!mHasCompatScaling) {
- return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
- start, end, start, end, mBidiFlags, null, 0));
+ int oldFlag = getFlags();
+ setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));
+ try {
+ if (!mHasCompatScaling) {
+ return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
+ start, end, start, end, mBidiFlags, null, 0));
+ }
+ final float oldSize = getTextSize();
+ setTextSize(oldSize * mCompatScaling);
+ final float w = nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags,
+ null, 0);
+ setTextSize(oldSize);
+ return (float) Math.ceil(w * mInvCompatScaling);
+ } finally {
+ setFlags(oldFlag);
}
- final float oldSize = getTextSize();
- setTextSize(oldSize * mCompatScaling);
- final float w = nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags,
- null, 0);
- setTextSize(oldSize);
- return (float) Math.ceil(w * mInvCompatScaling);
}
/**
@@ -2766,19 +2842,26 @@
if (text.length == 0 || count == 0) {
return 0;
}
- if (!mHasCompatScaling) {
- nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
- return count;
- }
+ int oldFlag = getFlags();
+ setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));
+ try {
+ if (!mHasCompatScaling) {
+ nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths,
+ 0);
+ return count;
+ }
- final float oldSize = getTextSize();
- setTextSize(oldSize * mCompatScaling);
- nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
- setTextSize(oldSize);
- for (int i = 0; i < count; i++) {
- widths[i] *= mInvCompatScaling;
+ final float oldSize = getTextSize();
+ setTextSize(oldSize * mCompatScaling);
+ nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
+ setTextSize(oldSize);
+ for (int i = 0; i < count; i++) {
+ widths[i] *= mInvCompatScaling;
+ }
+ return count;
+ } finally {
+ setFlags(oldFlag);
}
- return count;
}
/**
@@ -2849,19 +2932,25 @@
if (text.length() == 0 || start == end) {
return 0;
}
- if (!mHasCompatScaling) {
- nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
- return end - start;
- }
+ int oldFlag = getFlags();
+ setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));
+ try {
+ if (!mHasCompatScaling) {
+ nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
+ return end - start;
+ }
- final float oldSize = getTextSize();
- setTextSize(oldSize * mCompatScaling);
- nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
- setTextSize(oldSize);
- for (int i = 0; i < end - start; i++) {
- widths[i] *= mInvCompatScaling;
+ final float oldSize = getTextSize();
+ setTextSize(oldSize * mCompatScaling);
+ nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
+ setTextSize(oldSize);
+ for (int i = 0; i < end - start; i++) {
+ widths[i] *= mInvCompatScaling;
+ }
+ return end - start;
+ } finally {
+ setFlags(oldFlag);
}
- return end - start;
}
/**
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 2d33e8d..6da0719 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -269,6 +269,10 @@
* offset is zero. After the style is applied the internal offset is moved to {@code offset
* + length}, and next call will start from this new position.
*
+ * <p>
+ * {@link Paint#TEXT_RUN_FLAG_RIGHT_EDGE} and {@link Paint#TEXT_RUN_FLAG_LEFT_EDGE} are
+ * ignored and treated as both of them are set.
+ *
* @param paint a paint
* @param length a length to be applied with a given paint, can not exceed the length of the
* text
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 901d5fa..0e04658 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -1,13 +1,6 @@
package: "com.android.wm.shell"
flag {
- name: "example_flag"
- namespace: "multitasking"
- description: "An Example Flag"
- bug: "300136750"
-}
-
-flag {
name: "enable_app_pairs"
namespace: "multitasking"
description: "Enables the ability to create and save app pairs to the Home screen"
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index e4abae4..9854e58 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -134,6 +134,13 @@
<!-- Whether the additional education about reachability is enabled -->
<bool name="config_letterboxIsReachabilityEducationEnabled">false</bool>
+ <!-- The minimum tolerance of the percentage of activity bounds within its task to hide
+ size compat restart button. Value lower than 0 or higher than 100 will be ignored.
+ 100 is the default value where the activity has to fit exactly within the task to allow
+ size compat restart button to be hidden. 0 means size compat restart button will always
+ be hidden. -->
+ <integer name="config_letterboxRestartButtonHideTolerance">100</integer>
+
<!-- Whether DragAndDrop capability is enabled -->
<bool name="config_enableShellDragDrop">true</bool>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index e487328..bb0dd95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -769,8 +769,10 @@
boolean swapped = false;
for (int newIndex = 0; newIndex < bubbleViews.size(); newIndex++) {
View view = bubbleViews.get(newIndex);
- final int oldIndex = mLayout.indexOfChild(view);
- swapped |= animateSwap(view, oldIndex, newIndex, updateAllIcons, after);
+ if (view != null) {
+ final int oldIndex = mLayout.indexOfChild(view);
+ swapped |= animateSwap(view, oldIndex, newIndex, updateAllIcons, after);
+ }
}
if (!swapped) {
// All bubbles were at the right position. Make sure badges and z order is correct.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
index 09d99b2..fa2e236 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
@@ -71,6 +71,8 @@
private static final String HAS_SEEN_VERTICAL_REACHABILITY_EDUCATION_KEY_PREFIX =
"has_seen_vertical_reachability_education";
+ private static final int MAX_PERCENTAGE_VAL = 100;
+
/**
* The {@link SharedPreferences} instance for the restart dialog and the reachability
* education.
@@ -82,6 +84,12 @@
*/
private final SharedPreferences mLetterboxEduSharedPreferences;
+ /**
+ * The minimum tolerance of the percentage of activity bounds within its task to hide
+ * size compat restart button.
+ */
+ private final int mHideSizeCompatRestartButtonTolerance;
+
// Whether the extended restart dialog is enabled
private boolean mIsRestartDialogEnabled;
@@ -106,6 +114,9 @@
R.bool.config_letterboxIsRestartDialogEnabled);
mIsReachabilityEducationEnabled = context.getResources().getBoolean(
R.bool.config_letterboxIsReachabilityEducationEnabled);
+ final int tolerance = context.getResources().getInteger(
+ R.integer.config_letterboxRestartButtonHideTolerance);
+ mHideSizeCompatRestartButtonTolerance = getHideSizeCompatRestartButtonTolerance(tolerance);
mIsLetterboxRestartDialogAllowed = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG,
DEFAULT_VALUE_ENABLE_LETTERBOX_RESTART_DIALOG);
@@ -179,6 +190,10 @@
|| !hasSeenVerticalReachabilityEducation(taskInfo));
}
+ int getHideSizeCompatRestartButtonTolerance() {
+ return mHideSizeCompatRestartButtonTolerance;
+ }
+
boolean getHasSeenLetterboxEducation(int userId) {
return mLetterboxEduSharedPreferences
.getBoolean(dontShowLetterboxEduKey(userId), /* default= */ false);
@@ -218,6 +233,15 @@
}
}
+ // Returns the minimum tolerance of the percentage of activity bounds within its task to hide
+ // size compat restart button. Value lower than 0 or higher than 100 will be ignored.
+ // 100 is the default value where the activity has to fit exactly within the task to allow
+ // size compat restart button to be hidden. 0 means size compat restart button will always
+ // be hidden.
+ private int getHideSizeCompatRestartButtonTolerance(int tolerance) {
+ return tolerance < 0 || tolerance > MAX_PERCENTAGE_VAL ? MAX_PERCENTAGE_VAL : tolerance;
+ }
+
private boolean isReachabilityEducationEnabled() {
return mIsReachabilityEducationOverrideEnabled || (mIsReachabilityEducationEnabled
&& mIsLetterboxReachabilityEducationAllowed);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 00e0cdb..2dd2743 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -22,7 +22,9 @@
import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AppCompatTaskInfo;
import android.app.AppCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
import android.content.Context;
@@ -33,6 +35,7 @@
import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayLayout;
@@ -68,6 +71,8 @@
@VisibleForTesting
CompatUILayout mLayout;
+ private final float mHideScmTolerance;
+
CompatUIWindowManager(Context context, TaskInfo taskInfo,
SyncTransactionQueue syncQueue, CompatUICallback callback,
ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
@@ -75,11 +80,13 @@
Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartButtonClicked) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
mCallback = callback;
- mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
+ mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat
+ && shouldShowSizeCompatRestartButton(taskInfo);
mCameraCompatControlState = taskInfo.appCompatTaskInfo.cameraCompatControlState;
mCompatUIHintsState = compatUIHintsState;
mCompatUIConfiguration = compatUIConfiguration;
mOnRestartButtonClicked = onRestartButtonClicked;
+ mHideScmTolerance = mCompatUIConfiguration.getHideSizeCompatRestartButtonTolerance();
}
@Override
@@ -107,6 +114,11 @@
mLayout = inflateLayout();
mLayout.inject(this);
+ final TaskInfo taskInfo = getLastTaskInfo();
+ if (taskInfo != null) {
+ mHasSizeCompat = mHasSizeCompat && shouldShowSizeCompatRestartButton(taskInfo);
+ }
+
updateVisibilityOfViews();
if (mHasSizeCompat) {
@@ -127,7 +139,8 @@
boolean canShow) {
final boolean prevHasSizeCompat = mHasSizeCompat;
final int prevCameraCompatControlState = mCameraCompatControlState;
- mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
+ mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat
+ && shouldShowSizeCompatRestartButton(taskInfo);
mCameraCompatControlState = taskInfo.appCompatTaskInfo.cameraCompatControlState;
if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
@@ -208,6 +221,30 @@
updateSurfacePosition(positionX, positionY);
}
+ @VisibleForTesting
+ boolean shouldShowSizeCompatRestartButton(@NonNull TaskInfo taskInfo) {
+ if (!Flags.allowHideScmButton()) {
+ return true;
+ }
+ final AppCompatTaskInfo appCompatTaskInfo = taskInfo.appCompatTaskInfo;
+ final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
+ final int letterboxArea = computeArea(appCompatTaskInfo.topActivityLetterboxWidth,
+ appCompatTaskInfo.topActivityLetterboxHeight);
+ final int taskArea = computeArea(taskBounds.width(), taskBounds.height());
+ if (letterboxArea == 0 || taskArea == 0) {
+ return false;
+ }
+ final float percentageAreaOfLetterboxInTask = (float) letterboxArea / taskArea * 100;
+ return percentageAreaOfLetterboxInTask < mHideScmTolerance;
+ }
+
+ private int computeArea(int width, int height) {
+ if (width == 0 || height == 0) {
+ return 0;
+ }
+ return width * height;
+ }
+
private void updateVisibilityOfViews() {
if (mLayout == null) {
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 1a793a1..b2eeea7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -271,6 +271,9 @@
if (e.findPointerIndex(mDragPointerId) == -1) {
mDragPointerId = e.getPointerId(0);
}
+ final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ // If a decor's resize drag zone is active, don't also try to reposition it.
+ if (decoration.isHandlingDragResize()) break;
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 1debb02..5a74255 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -286,6 +286,10 @@
closeBackground.setTintList(buttonTintColor);
}
+ boolean isHandlingDragResize() {
+ return mDragResizeListener.isHandlingDragResize();
+ }
+
private void closeDragResizeListener() {
if (mDragResizeListener == null) {
return;
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 aabc1cf..554b1fb 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
@@ -491,8 +491,11 @@
return true;
}
case MotionEvent.ACTION_MOVE: {
+ mShouldClick = false;
final DesktopModeWindowDecoration decoration =
mWindowDecorByTaskId.get(mTaskId);
+ // If a decor's resize drag zone is active, don't also try to reposition it.
+ if (decoration.isHandlingDragResize()) break;
decoration.closeMaximizeMenu();
if (e.findPointerIndex(mDragPointerId) == -1) {
mDragPointerId = e.getPointerId(0);
@@ -505,7 +508,6 @@
e.getRawX(dragPointerIdx),
newTaskBounds));
mIsDragging = true;
- mShouldClick = false;
return true;
}
case MotionEvent.ACTION_UP:
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 53f806c..2023333 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
@@ -387,6 +387,10 @@
return mHandleMenu != null;
}
+ boolean isHandlingDragResize() {
+ return mDragResizeListener.isHandlingDragResize();
+ }
+
private void loadAppInfo() {
String packageName = mTaskInfo.realActivity.getPackageName();
PackageManager pm = mContext.getApplicationContext().getPackageManager();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 8511a21..8b38f99 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -320,6 +320,10 @@
}
}
+ boolean isHandlingDragResize() {
+ return mInputEventReceiver.isHandlingEvents();
+ }
+
@Override
public void close() {
mInputEventReceiver.dispose();
@@ -386,6 +390,10 @@
finishInputEvent(inputEvent, handleInputEvent(inputEvent));
}
+ boolean isHandlingEvents() {
+ return mShouldHandleEvents;
+ }
+
private boolean handleInputEvent(InputEvent inputEvent) {
if (!(inputEvent instanceof MotionEvent)) {
return false;
@@ -409,7 +417,6 @@
mShouldHandleEvents = isInResizeHandleBounds(x, y);
}
if (mShouldHandleEvents) {
- mInputManager.pilferPointers(mInputChannel.getToken());
mDragPointerId = e.getPointerId(0);
float rawX = e.getRawX(0);
float rawY = e.getRawY(0);
@@ -427,6 +434,7 @@
if (!mShouldHandleEvents) {
break;
}
+ mInputManager.pilferPointers(mInputChannel.getToken());
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
float rawX = e.getRawX(dragPointerIndex);
float rawY = e.getRawY(dragPointerIndex);
@@ -437,6 +445,7 @@
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
+ mInputManager.pilferPointers(mInputChannel.getToken());
if (mShouldHandleEvents) {
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
final Rect taskBounds = mCallback.onDragPositioningEnd(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index c1b18f9..7c6e69e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -61,6 +61,7 @@
private int mCtrlType;
private boolean mIsResizingOrAnimatingResize;
@Surface.Rotation private int mRotation;
+ private boolean mVeilIsVisible;
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
DesktopModeWindowDecoration windowDecoration,
@@ -94,7 +95,6 @@
mDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
mRepositionStartPoint.set(x, y);
if (isResizing()) {
- mDesktopWindowDecoration.showResizeVeil(mTaskBoundsAtDragStart);
if (!mDesktopWindowDecoration.mTaskInfo.isFocused) {
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true);
@@ -119,8 +119,13 @@
if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType,
mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta,
mDisplayController, mDesktopWindowDecoration)) {
- mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
mIsResizingOrAnimatingResize = true;
+ if (!mVeilIsVisible) {
+ mDesktopWindowDecoration.showResizeVeil(mRepositionTaskBounds);
+ mVeilIsVisible = true;
+ } else {
+ mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
+ }
} else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
final SurfaceControl.Transaction t = mTransactionSupplier.get();
DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration,
@@ -143,7 +148,7 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
- } else {
+ } else if (mVeilIsVisible) {
// If bounds haven't changed, perform necessary veil reset here as startAnimation
// won't be called.
mDesktopWindowDecoration.hideResizeVeil();
@@ -163,6 +168,7 @@
mCtrlType = CTRL_TYPE_UNDEFINED;
mTaskBoundsAtDragStart.setEmpty();
mRepositionStartPoint.set(0, 0);
+ mVeilIsVisible = false;
return new Rect(mRepositionTaskBounds);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index d4b97ed..2acfd83 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -20,6 +20,8 @@
import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.view.WindowInsets.Type.navigationBars;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -27,6 +29,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
@@ -39,6 +42,7 @@
import android.app.TaskInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.DisplayInfo;
@@ -50,6 +54,7 @@
import androidx.test.filters.SmallTest;
+import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
@@ -59,6 +64,7 @@
import junit.framework.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -76,6 +82,8 @@
@RunWith(AndroidTestingRunner.class)
@SmallTest
public class CompatUIWindowManagerTest extends ShellTestCase {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
private static final int TASK_ID = 1;
@@ -107,6 +115,7 @@
public void testCreateSizeCompatButton() {
// Doesn't create layout if show is false.
mWindowManager.mHasSizeCompat = true;
+ doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(mTaskInfo);
assertTrue(mWindowManager.createLayout(/* canShow= */ false));
verify(mWindowManager, never()).inflateLayout();
@@ -199,6 +208,7 @@
// No diff
clearInvocations(mWindowManager);
TaskInfo taskInfo = createTaskInfo(/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
+ doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(any());
assertTrue(mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true));
verify(mWindowManager, never()).updateSurfacePosition();
@@ -284,6 +294,7 @@
@Test
public void testUpdateCompatInfoLayoutNotInflatedYet() {
mWindowManager.mHasSizeCompat = true;
+ doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(any());
mWindowManager.createLayout(/* canShow= */ false);
verify(mWindowManager, never()).inflateLayout();
@@ -353,6 +364,7 @@
// Create button if it is not created.
mWindowManager.mLayout = null;
mWindowManager.mHasSizeCompat = true;
+ doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(mTaskInfo);
mWindowManager.updateVisibility(/* canShow= */ true);
verify(mWindowManager).createLayout(/* canShow= */ true);
@@ -464,6 +476,37 @@
Assert.assertTrue(mWindowManager.needsToBeRecreated(newTaskInfo, mTaskListener));
}
+ @Test
+ public void testShouldShowSizeCompatRestartButton() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_HIDE_SCM_BUTTON);
+
+ doReturn(86).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
+ mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
+ mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+ mCompatUIConfiguration, mOnRestartButtonClicked);
+
+ // Simulate rotation of activity in square display
+ TaskInfo taskInfo = createTaskInfo(true, CAMERA_COMPAT_CONTROL_HIDDEN);
+ taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000));
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 2000;
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1850;
+
+ assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+
+ // Simulate exiting split screen/folding
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
+ assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+
+ // Simulate folding
+ taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 1000, 2000));
+ assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 500;
+ assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+ }
+
private static TaskInfo createTaskInfo(boolean hasSizeCompat,
@AppCompatTaskInfo.CameraCompatControlState int cameraCompatControlState) {
ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 0841210..86253f3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -144,13 +144,13 @@
}
@Test
- fun testDragResize_noMove_showsResizeVeil() {
+ fun testDragResize_noMove_doesNotShowResizeVeil() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
- verify(mockDesktopWindowDecoration).showResizeVeil(STARTING_BOUNDS)
+ verify(mockDesktopWindowDecoration, never()).showResizeVeil(STARTING_BOUNDS)
taskPositioner.onDragPositioningEnd(
STARTING_BOUNDS.left.toFloat(),
@@ -162,7 +162,7 @@
(change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
change.configuration.windowConfiguration.bounds == STARTING_BOUNDS}},
eq(taskPositioner))
- verify(mockDesktopWindowDecoration).hideResizeVeil()
+ verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
}
@Test
@@ -212,7 +212,6 @@
STARTING_BOUNDS.right.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
- verify(mockDesktopWindowDecoration).showResizeVeil(STARTING_BOUNDS)
taskPositioner.onDragPositioningMove(
STARTING_BOUNDS.right.toFloat() + 10,
@@ -222,6 +221,7 @@
val rectAfterMove = Rect(STARTING_BOUNDS)
rectAfterMove.right += 10
rectAfterMove.top += 10
+ verify(mockDesktopWindowDecoration).showResizeVeil(rectAfterMove)
verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
@@ -237,7 +237,7 @@
val rectAfterEnd = Rect(rectAfterMove)
rectAfterEnd.right += 10
rectAfterEnd.top += 10
- verify(mockDesktopWindowDecoration, times(2)).updateResizeVeil(any())
+ verify(mockDesktopWindowDecoration).updateResizeVeil(any())
verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
@@ -253,7 +253,6 @@
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
- verify(mockDesktopWindowDecoration).showResizeVeil(STARTING_BOUNDS)
taskPositioner.onDragPositioningMove(
STARTING_BOUNDS.left.toFloat(),
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index c9d5e07..d9166a1 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -21,6 +21,7 @@
#include <algorithm>
#include <cstddef>
#include <limits>
+#include <optional>
#include "android-base/logging.h"
#include "android-base/stringprintf.h"
@@ -50,7 +51,9 @@
// contiguous block of memory to store both the TypeSpec struct and
// the Type structs.
struct TypeSpecBuilder {
- explicit TypeSpecBuilder(incfs::verified_map_ptr<ResTable_typeSpec> header) : header_(header) {}
+ explicit TypeSpecBuilder(incfs::verified_map_ptr<ResTable_typeSpec> header) : header_(header) {
+ type_entries.reserve(dtohs(header_->typesCount));
+ }
void AddType(incfs::verified_map_ptr<ResTable_type> type) {
TypeSpec::TypeEntry& entry = type_entries.emplace_back();
@@ -59,6 +62,7 @@
}
TypeSpec Build() {
+ type_entries.shrink_to_fit();
return {header_, std::move(type_entries)};
}
@@ -450,7 +454,8 @@
std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
package_property_t property_flags) {
ATRACE_NAME("LoadedPackage::Load");
- std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage());
+ const bool optimize_name_lookups = (property_flags & PROPERTY_OPTIMIZE_NAME_LOOKUPS) != 0;
+ std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage(optimize_name_lookups));
// typeIdOffset was added at some point, but we still must recognize apps built before this
// was added.
@@ -499,7 +504,7 @@
// A map of TypeSpec builders, each associated with an type index.
// We use these to accumulate the set of Types available for a TypeSpec, and later build a single,
// contiguous block of memory that holds all the Types together with the TypeSpec.
- std::unordered_map<int, std::unique_ptr<TypeSpecBuilder>> type_builder_map;
+ std::unordered_map<int, std::optional<TypeSpecBuilder>> type_builder_map;
ChunkIterator iter(chunk.data_ptr(), chunk.data_size());
while (iter.HasNext()) {
@@ -567,14 +572,14 @@
return {};
}
- if (entry_count * sizeof(uint32_t) > chunk.data_size()) {
+ if (entry_count * sizeof(uint32_t) > child_chunk.data_size()) {
LOG(ERROR) << "RES_TABLE_TYPE_SPEC_TYPE too small to hold entries.";
return {};
}
- std::unique_ptr<TypeSpecBuilder>& builder_ptr = type_builder_map[type_spec->id];
- if (builder_ptr == nullptr) {
- builder_ptr = util::make_unique<TypeSpecBuilder>(type_spec.verified());
+ auto& maybe_type_builder = type_builder_map[type_spec->id];
+ if (!maybe_type_builder) {
+ maybe_type_builder.emplace(type_spec.verified());
loaded_package->resource_ids_.set(type_spec->id, entry_count);
} else {
LOG(WARNING) << StringPrintf("RES_TABLE_TYPE_SPEC_TYPE already defined for ID %02x",
@@ -594,9 +599,9 @@
}
// Type chunks must be preceded by their TypeSpec chunks.
- std::unique_ptr<TypeSpecBuilder>& builder_ptr = type_builder_map[type->id];
- if (builder_ptr != nullptr) {
- builder_ptr->AddType(type.verified());
+ auto& maybe_type_builder = type_builder_map[type->id];
+ if (maybe_type_builder) {
+ maybe_type_builder->AddType(type.verified());
} else {
LOG(ERROR) << StringPrintf(
"RES_TABLE_TYPE_TYPE with ID %02x found without preceding RES_TABLE_TYPE_SPEC_TYPE.",
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 4c992be..2c99f1a 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -447,15 +447,19 @@
// --------------------------------------------------------------------
// --------------------------------------------------------------------
-ResStringPool::ResStringPool()
- : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL)
-{
+ResStringPool::ResStringPool() : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL) {
}
-ResStringPool::ResStringPool(const void* data, size_t size, bool copyData)
- : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL)
-{
- setTo(data, size, copyData);
+ResStringPool::ResStringPool(bool optimize_name_lookups) : ResStringPool() {
+ if (optimize_name_lookups) {
+ mIndexLookupCache.emplace();
+ }
+}
+
+ResStringPool::ResStringPool(const void* data, size_t size, bool copyData,
+ bool optimize_name_lookups)
+ : ResStringPool(optimize_name_lookups) {
+ setTo(data, size, copyData);
}
ResStringPool::~ResStringPool()
@@ -683,6 +687,14 @@
mStylePoolSize = 0;
}
+ if (mIndexLookupCache) {
+ if ((mHeader->flags & ResStringPool_header::UTF8_FLAG) != 0) {
+ mIndexLookupCache->first.reserve(mHeader->stringCount);
+ } else {
+ mIndexLookupCache->second.reserve(mHeader->stringCount);
+ }
+ }
+
return (mError=NO_ERROR);
}
@@ -708,6 +720,10 @@
free(mOwnedData);
mOwnedData = NULL;
}
+ if (mIndexLookupCache) {
+ mIndexLookupCache->first.clear();
+ mIndexLookupCache->second.clear();
+ }
}
/**
@@ -824,11 +840,11 @@
// encLen must be less than 0x7FFF due to encoding.
if ((uint32_t)(u8str+*u8len-strings) < mStringPoolSize) {
- AutoMutex lock(mDecodeLock);
+ AutoMutex lock(mCachesLock);
- if (mCache != NULL && mCache[idx] != NULL) {
- return StringPiece16(mCache[idx], *u16len);
- }
+ if (mCache != NULL && mCache[idx] != NULL) {
+ return StringPiece16(mCache[idx], *u16len);
+ }
// Retrieve the actual length of the utf8 string if the
// encoded length was truncated
@@ -1093,12 +1109,24 @@
// block, start searching at the back.
String8 str8(str, strLen);
const size_t str8Len = str8.size();
+ std::optional<AutoMutex> cacheLock;
+ if (mIndexLookupCache) {
+ cacheLock.emplace(mCachesLock);
+ if (auto it = mIndexLookupCache->first.find(std::string_view(str8));
+ it != mIndexLookupCache->first.end()) {
+ return it->second;
+ }
+ }
+
for (int i=mHeader->stringCount-1; i>=0; i--) {
const base::expected<StringPiece, NullOrIOError> s = string8At(i);
if (UNLIKELY(IsIOError(s))) {
return base::unexpected(s.error());
}
if (s.has_value()) {
+ if (mIndexLookupCache) {
+ mIndexLookupCache->first.insert({*s, i});
+ }
if (kDebugStringPoolNoisy) {
ALOGI("Looking at %s, i=%d\n", s->data(), i);
}
@@ -1151,20 +1179,32 @@
// most often this happens because we want to get IDs for style
// span tags; since those always appear at the end of the string
// block, start searching at the back.
+ std::optional<AutoMutex> cacheLock;
+ if (mIndexLookupCache) {
+ cacheLock.emplace(mCachesLock);
+ if (auto it = mIndexLookupCache->second.find({str, strLen});
+ it != mIndexLookupCache->second.end()) {
+ return it->second;
+ }
+ }
for (int i=mHeader->stringCount-1; i>=0; i--) {
const base::expected<StringPiece16, NullOrIOError> s = stringAt(i);
if (UNLIKELY(IsIOError(s))) {
return base::unexpected(s.error());
}
if (kDebugStringPoolNoisy) {
- ALOGI("Looking at %s, i=%d\n", String8(s->data(), s->size()).c_str(), i);
+ ALOGI("Looking16 at %s, i=%d\n", String8(s->data(), s->size()).c_str(), i);
}
- if (s.has_value() && strLen == s->size() &&
- strzcmp16(s->data(), s->size(), str, strLen) == 0) {
+ if (s.has_value()) {
+ if (mIndexLookupCache) {
+ mIndexLookupCache->second.insert({*s, i});
+ }
+ if (strLen == s->size() && strzcmp16(s->data(), s->size(), str, strLen) == 0) {
if (kDebugStringPoolNoisy) {
- ALOGI("MATCH!");
+ ALOGI("MATCH16!");
}
return i;
+ }
}
}
}
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index 6068912..d9f7c2a 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -71,7 +71,7 @@
// Rewrites a compile-time overlay resource id to the runtime resource id of corresponding target
// resource.
- virtual status_t lookupResourceIdNoRewrite(uint32_t* resId) const;
+ status_t lookupResourceIdNoRewrite(uint32_t* resId) const;
const Idmap_data_header* data_header_;
const Idmap_overlay_entry* entries_;
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 3a72871..413b278 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -99,6 +99,9 @@
// The apk assets only contain the overlayable declarations information.
PROPERTY_ONLY_OVERLAYABLES = 1U << 5U,
+
+ // Optimize the resource lookups by name via an in-memory lookup table.
+ PROPERTY_OPTIMIZE_NAME_LOOKUPS = 1U << 6U,
};
struct OverlayableInfo {
@@ -285,7 +288,9 @@
private:
DISALLOW_COPY_AND_ASSIGN(LoadedPackage);
- LoadedPackage() = default;
+ explicit LoadedPackage(bool optimize_name_lookups = false)
+ : type_string_pool_(optimize_name_lookups), key_string_pool_(optimize_name_lookups) {
+ }
ResStringPool type_string_pool_;
ResStringPool key_string_pool_;
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index c0514fd..3d1403d 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -22,27 +22,28 @@
#include <android-base/expected.h>
#include <android-base/unique_fd.h>
-
+#include <android/configuration.h>
#include <androidfw/Asset.h>
#include <androidfw/Errors.h>
#include <androidfw/LocaleData.h>
#include <androidfw/StringPiece.h>
#include <utils/ByteOrder.h>
#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
#include <utils/String16.h>
#include <utils/Vector.h>
-#include <utils/KeyedVector.h>
-
#include <utils/threads.h>
#include <stdint.h>
#include <sys/types.h>
-#include <android/configuration.h>
-
#include <array>
#include <map>
#include <memory>
+#include <optional>
+#include <string_view>
+#include <unordered_map>
+#include <utility>
namespace android {
@@ -512,23 +513,24 @@
class ResStringPool
{
public:
- ResStringPool();
- ResStringPool(const void* data, size_t size, bool copyData=false);
- virtual ~ResStringPool();
+ ResStringPool();
+ explicit ResStringPool(bool optimize_name_lookups);
+ ResStringPool(const void* data, size_t size, bool copyData = false,
+ bool optimize_name_lookups = false);
+ virtual ~ResStringPool();
- void setToEmpty();
- status_t setTo(incfs::map_ptr<void> data, size_t size, bool copyData=false);
+ void setToEmpty();
+ status_t setTo(incfs::map_ptr<void> data, size_t size, bool copyData = false);
- status_t getError() const;
+ status_t getError() const;
- void uninit();
+ void uninit();
- // Return string entry as UTF16; if the pool is UTF8, the string will
- // be converted before returning.
- inline base::expected<StringPiece16, NullOrIOError> stringAt(
- const ResStringPool_ref& ref) const {
- return stringAt(ref.index);
- }
+ // Return string entry as UTF16; if the pool is UTF8, the string will
+ // be converted before returning.
+ inline base::expected<StringPiece16, NullOrIOError> stringAt(const ResStringPool_ref& ref) const {
+ return stringAt(ref.index);
+ }
virtual base::expected<StringPiece16, NullOrIOError> stringAt(size_t idx) const;
// Note: returns null if the string pool is not UTF8.
@@ -557,7 +559,7 @@
void* mOwnedData;
incfs::verified_map_ptr<ResStringPool_header> mHeader;
size_t mSize;
- mutable Mutex mDecodeLock;
+ mutable Mutex mCachesLock;
incfs::map_ptr<uint32_t> mEntries;
incfs::map_ptr<uint32_t> mEntryStyles;
incfs::map_ptr<void> mStrings;
@@ -566,6 +568,10 @@
incfs::map_ptr<uint32_t> mStyles;
uint32_t mStylePoolSize; // number of uint32_t
+ mutable std::optional<std::pair<std::unordered_map<std::string_view, int>,
+ std::unordered_map<std::u16string_view, int>>>
+ mIndexLookupCache;
+
base::expected<StringPiece, NullOrIOError> stringDecodeAt(
size_t idx, incfs::map_ptr<uint8_t> str, size_t encLen) const;
};
@@ -1401,8 +1407,8 @@
// Must be 0.
uint8_t res0;
- // Must be 0.
- uint16_t res1;
+ // Used to be reserved, if >0 specifies the number of ResTable_type entries for this spec.
+ uint16_t typesCount;
// Number of uint32_t entry configuration masks that follow.
uint32_t entryCount;
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index 00d049c..6ebfc63 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -41,6 +41,14 @@
#endif // __ANDROID__
}
+inline bool inter_character_justification() {
+#ifdef __ANDROID__
+ return com_android_text_flags_inter_character_justification();
+#else
+ return true;
+#endif // __ANDROID__
+}
+
} // namespace text_feature
} // namespace android
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index 833069f..5613369 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -72,10 +72,13 @@
const minikin::Range contextRange(contextStart, contextStart + contextCount);
const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
+ const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification()
+ ? paint->getRunFlag()
+ : minikin::RunFlag::NONE;
if (mt == nullptr) {
return minikin::Layout(textBuf.substr(contextRange), range - contextStart, bidiFlags,
- minikinPaint, startHyphen, endHyphen);
+ minikinPaint, startHyphen, endHyphen, minikinRunFlag);
} else {
return mt->buildLayout(textBuf, range, contextRange, minikinPaint, startHyphen, endHyphen);
}
@@ -102,9 +105,12 @@
const minikin::Range range(start, start + count);
const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
+ const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification()
+ ? paint->getRunFlag()
+ : minikin::RunFlag::NONE;
return minikin::Layout::measureText(textBuf, range, bidiFlags, minikinPaint, startHyphen,
- endHyphen, advances, bounds, clusterCount);
+ endHyphen, advances, bounds, clusterCount, minikinRunFlag);
}
minikin::MinikinExtent MinikinUtils::getFontExtent(const Paint* paint, minikin::Bidi bidiFlags,
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index ef4dce5..708f96e 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -25,6 +25,7 @@
#include <minikin/FontFamily.h>
#include <minikin/FontFeature.h>
#include <minikin/Hyphenator.h>
+#include <minikin/Layout.h>
#include <string>
@@ -144,6 +145,9 @@
bool isDevKern() const { return mDevKern; }
void setDevKern(bool d) { mDevKern = d; }
+ minikin::RunFlag getRunFlag() const { return mRunFlag; }
+ void setRunFlag(minikin::RunFlag runFlag) { mRunFlag = runFlag; }
+
// Deprecated -- bitmapshaders will be taking this flag explicitly
bool isFilterBitmap() const { return mFilterBitmap; }
void setFilterBitmap(bool filter) { mFilterBitmap = filter; }
@@ -188,6 +192,7 @@
bool mStrikeThru = false;
bool mUnderline = false;
bool mDevKern = false;
+ minikin::RunFlag mRunFlag = minikin::RunFlag::NONE;
};
} // namespace android
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index aac928f..c32ea01 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -47,8 +47,8 @@
, mFilterBitmap(paint.mFilterBitmap)
, mStrikeThru(paint.mStrikeThru)
, mUnderline(paint.mUnderline)
- , mDevKern(paint.mDevKern) {}
-
+ , mDevKern(paint.mDevKern)
+ , mRunFlag(paint.mRunFlag) {}
Paint::~Paint() {}
@@ -68,21 +68,19 @@
mStrikeThru = other.mStrikeThru;
mUnderline = other.mUnderline;
mDevKern = other.mDevKern;
+ mRunFlag = other.mRunFlag;
return *this;
}
bool operator==(const Paint& a, const Paint& b) {
- return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) &&
- a.mFont == b.mFont &&
- a.mLooper == b.mLooper &&
- a.mLetterSpacing == b.mLetterSpacing && a.mWordSpacing == b.mWordSpacing &&
- a.mFontFeatureSettings == b.mFontFeatureSettings &&
+ return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) && a.mFont == b.mFont &&
+ a.mLooper == b.mLooper && a.mLetterSpacing == b.mLetterSpacing &&
+ a.mWordSpacing == b.mWordSpacing && a.mFontFeatureSettings == b.mFontFeatureSettings &&
a.mMinikinLocaleListId == b.mMinikinLocaleListId &&
a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit &&
a.mTypeface == b.mTypeface && a.mAlign == b.mAlign &&
- a.mFilterBitmap == b.mFilterBitmap &&
- a.mStrikeThru == b.mStrikeThru && a.mUnderline == b.mUnderline &&
- a.mDevKern == b.mDevKern;
+ a.mFilterBitmap == b.mFilterBitmap && a.mStrikeThru == b.mStrikeThru &&
+ a.mUnderline == b.mUnderline && a.mDevKern == b.mDevKern && a.mRunFlag == b.mRunFlag;
}
void Paint::reset() {
@@ -96,6 +94,7 @@
mStrikeThru = false;
mUnderline = false;
mDevKern = false;
+ mRunFlag = minikin::RunFlag::NONE;
}
void Paint::setLooper(sk_sp<BlurDrawLooper> looper) {
@@ -133,6 +132,8 @@
// flags related to minikin::Paint
static const uint32_t sUnderlineFlag = 0x08;
static const uint32_t sStrikeThruFlag = 0x10;
+static const uint32_t sTextRunLeftEdge = 0x2000;
+static const uint32_t sTextRunRightEdge = 0x4000;
// flags no longer supported on native side (but mirrored for compatibility)
static const uint32_t sDevKernFlag = 0x100;
@@ -186,6 +187,12 @@
flags |= -(int)mUnderline & sUnderlineFlag;
flags |= -(int)mDevKern & sDevKernFlag;
flags |= -(int)mFilterBitmap & sFilterBitmapFlag;
+ if (mRunFlag & minikin::RunFlag::LEFT_EDGE) {
+ flags |= sTextRunLeftEdge;
+ }
+ if (mRunFlag & minikin::RunFlag::RIGHT_EDGE) {
+ flags |= sTextRunRightEdge;
+ }
return flags;
}
@@ -196,6 +203,15 @@
mUnderline = (flags & sUnderlineFlag) != 0;
mDevKern = (flags & sDevKernFlag) != 0;
mFilterBitmap = (flags & sFilterBitmapFlag) != 0;
+
+ std::underlying_type<minikin::RunFlag>::type rawFlag = minikin::RunFlag::NONE;
+ if (flags & sTextRunLeftEdge) {
+ rawFlag |= minikin::RunFlag::LEFT_EDGE;
+ }
+ if (flags & sTextRunRightEdge) {
+ rawFlag |= minikin::RunFlag::RIGHT_EDGE;
+ }
+ mRunFlag = static_cast<minikin::RunFlag>(rawFlag);
}
} // namespace android
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 8ba7503..d572593 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -613,6 +613,12 @@
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
const Typeface* typeface = paint->getAndroidTypeface();
ScopedCharArrayRO text(env, charArray);
+
+ // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+ // text as entire line mode.
+ const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+ paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
// drawTextString and drawTextChars doesn't use context info
get_canvas(canvasHandle)->drawText(
text.get() + index, count, // text buffer
@@ -620,6 +626,7 @@
0, count, // context range
x, y, // draw position
static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr /* measured text */);
+ paint->setRunFlag(originalRunFlag);
}
static void drawTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring strObj,
@@ -629,6 +636,12 @@
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
const Typeface* typeface = paint->getAndroidTypeface();
const int count = end - start;
+
+ // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+ // text as entire line mode.
+ const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+ paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
// drawTextString and drawTextChars doesn't use context info
get_canvas(canvasHandle)->drawText(
text.get() + start, count, // text buffer
@@ -636,6 +649,7 @@
0, count, // context range
x, y, // draw position
static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr /* measured text */);
+ paint->setRunFlag(originalRunFlag);
}
static void drawTextRunChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray charArray,
@@ -681,9 +695,15 @@
jchar* jchars = env->GetCharArrayElements(text, NULL);
+ // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+ // text as entire line mode.
+ const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+ paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
get_canvas(canvasHandle)->drawTextOnPath(jchars + index, count,
static_cast<minikin::Bidi>(bidiFlags), *path, hOffset, vOffset, *paint, typeface);
+ paint->setRunFlag(originalRunFlag);
env->ReleaseCharArrayElements(text, jchars, 0);
}
@@ -697,9 +717,15 @@
const jchar* jchars = env->GetStringChars(text, NULL);
int count = env->GetStringLength(text);
+ // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+ // text as entire line mode.
+ const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+ paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
get_canvas(canvasHandle)->drawTextOnPath(jchars, count, static_cast<minikin::Bidi>(bidiFlags),
*path, hOffset, vOffset, *paint, typeface);
+ paint->setRunFlag(originalRunFlag);
env->ReleaseStringChars(text, jchars);
}
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index eb4d494..ac2a936 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -124,24 +124,19 @@
// flush and submit all work to the gpu and wait for it to finish
mGrContext->flushAndSubmit(GrSyncCpu::kYes);
- switch (mode) {
- case TrimLevel::BACKGROUND:
- mGrContext->freeGpuResources();
- SkGraphics::PurgeAllCaches();
- mRenderThread.destroyRenderingContext();
- break;
- case TrimLevel::UI_HIDDEN:
- // Here we purge all the unlocked scratch resources and then toggle the resources cache
- // limits between the background and max amounts. This causes the unlocked resources
- // that have persistent data to be purged in LRU order.
- mGrContext->setResourceCacheLimit(mBackgroundResourceBytes);
- SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes);
- mGrContext->purgeUnlockedResources(toSkiaEnum(mMemoryPolicy.purgeScratchOnly));
- mGrContext->setResourceCacheLimit(mMaxResourceBytes);
- SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
- break;
- default:
- break;
+ if (mode >= TrimLevel::BACKGROUND) {
+ mGrContext->freeGpuResources();
+ SkGraphics::PurgeAllCaches();
+ mRenderThread.destroyRenderingContext();
+ } else if (mode == TrimLevel::UI_HIDDEN) {
+ // Here we purge all the unlocked scratch resources and then toggle the resources cache
+ // limits between the background and max amounts. This causes the unlocked resources
+ // that have persistent data to be purged in LRU order.
+ mGrContext->setResourceCacheLimit(mBackgroundResourceBytes);
+ SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes);
+ mGrContext->purgeUnlockedResources(toSkiaEnum(mMemoryPolicy.purgeScratchOnly));
+ mGrContext->setResourceCacheLimit(mMaxResourceBytes);
+ SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
}
}
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index b002bbf..ea136ed 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -1079,7 +1079,7 @@
* @return one of the values that can be set in {@link Builder#setEncoding(int)} or
* {@link AudioFormat#ENCODING_INVALID} if not set.
*/
- public int getEncoding() {
+ public @Encoding int getEncoding() {
return mEncoding;
}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 09f09b9..f74edbf 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -30,6 +30,7 @@
import android.hardware.tv.tuner.Constant;
import android.hardware.tv.tuner.Constant64Bit;
import android.hardware.tv.tuner.FrontendScanType;
+import android.media.MediaCodec;
import android.media.tv.TvInputService;
import android.media.tv.tuner.dvr.DvrPlayback;
import android.media.tv.tuner.dvr.DvrRecorder;
@@ -272,8 +273,12 @@
try {
System.loadLibrary("media_tv_tuner");
nativeInit();
+ // Load and initialize MediaCodec to avoid flaky cts test result.
+ Class.forName(MediaCodec.class.getName());
} catch (UnsatisfiedLinkError e) {
Log.d(TAG, "tuner JNI library not found!");
+ } catch (ClassNotFoundException e) {
+ Log.e(TAG, "MediaCodec class not found!", e);
}
}
diff --git a/packages/SettingsLib/ActionBarShadow/Android.bp b/packages/SettingsLib/ActionBarShadow/Android.bp
index 6f94458..77cbb00 100644
--- a/packages/SettingsLib/ActionBarShadow/Android.bp
+++ b/packages/SettingsLib/ActionBarShadow/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibActionBarShadow",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
diff --git a/packages/SettingsLib/ActionButtonsPreference/Android.bp b/packages/SettingsLib/ActionButtonsPreference/Android.bp
index 1228555..c36b82d 100644
--- a/packages/SettingsLib/ActionButtonsPreference/Android.bp
+++ b/packages/SettingsLib/ActionButtonsPreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibActionButtonsPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/ActivityEmbedding/Android.bp b/packages/SettingsLib/ActivityEmbedding/Android.bp
index 41de29a..838a9e5 100644
--- a/packages/SettingsLib/ActivityEmbedding/Android.bp
+++ b/packages/SettingsLib/ActivityEmbedding/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibActivityEmbedding",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
diff --git a/packages/SettingsLib/AdaptiveIcon/Android.bp b/packages/SettingsLib/AdaptiveIcon/Android.bp
index 044ba87..67b6fb5 100644
--- a/packages/SettingsLib/AdaptiveIcon/Android.bp
+++ b/packages/SettingsLib/AdaptiveIcon/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibAdaptiveIcon",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 5da4b95..c2cb757 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -9,6 +9,9 @@
android_library {
name: "SettingsLib",
+ defaults: [
+ "SettingsLintDefaults",
+ ],
static_libs: [
"androidx.localbroadcastmanager_localbroadcastmanager",
@@ -60,8 +63,15 @@
"src/**/*.java",
"src/**/*.kt",
],
+}
+
+// defaults for lint option
+java_defaults {
+ name: "SettingsLintDefaults",
lint: {
- extra_check_modules: ["SettingsLibLintChecker"],
+ extra_check_modules: [
+ "SettingsLibLintChecker",
+ ],
},
}
diff --git a/packages/SettingsLib/AppPreference/Android.bp b/packages/SettingsLib/AppPreference/Android.bp
index 69b9d44..c5b2ef6 100644
--- a/packages/SettingsLib/AppPreference/Android.bp
+++ b/packages/SettingsLib/AppPreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibAppPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/BannerMessagePreference/Android.bp b/packages/SettingsLib/BannerMessagePreference/Android.bp
index da91344..07290de 100644
--- a/packages/SettingsLib/BannerMessagePreference/Android.bp
+++ b/packages/SettingsLib/BannerMessagePreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibBannerMessagePreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/BarChartPreference/Android.bp b/packages/SettingsLib/BarChartPreference/Android.bp
index be1e0cf..448ed56 100644
--- a/packages/SettingsLib/BarChartPreference/Android.bp
+++ b/packages/SettingsLib/BarChartPreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibBarChartPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/ButtonPreference/Android.bp b/packages/SettingsLib/ButtonPreference/Android.bp
index 35572fad..0382829 100644
--- a/packages/SettingsLib/ButtonPreference/Android.bp
+++ b/packages/SettingsLib/ButtonPreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibButtonPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
index 70f7554..87ec0b8 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibCollapsingToolbarBaseActivity",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/DisplayUtils/Android.bp b/packages/SettingsLib/DisplayUtils/Android.bp
index eab35a1..279bb70 100644
--- a/packages/SettingsLib/DisplayUtils/Android.bp
+++ b/packages/SettingsLib/DisplayUtils/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibDisplayUtils",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
diff --git a/packages/SettingsLib/EntityHeaderWidgets/Android.bp b/packages/SettingsLib/EntityHeaderWidgets/Android.bp
index 17b662c..83f81c6 100644
--- a/packages/SettingsLib/EntityHeaderWidgets/Android.bp
+++ b/packages/SettingsLib/EntityHeaderWidgets/Android.bp
@@ -10,13 +10,16 @@
android_library {
name: "SettingsLibEntityHeaderWidgets",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
static_libs: [
- "androidx.annotation_annotation",
- "SettingsLibSettingsTheme"
+ "androidx.annotation_annotation",
+ "SettingsLibSettingsTheme",
],
sdk_version: "system_current",
diff --git a/packages/SettingsLib/FooterPreference/Android.bp b/packages/SettingsLib/FooterPreference/Android.bp
index b45cd65..d1ad80d 100644
--- a/packages/SettingsLib/FooterPreference/Android.bp
+++ b/packages/SettingsLib/FooterPreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibFooterPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/HelpUtils/Android.bp b/packages/SettingsLib/HelpUtils/Android.bp
index 041fce2..284106e 100644
--- a/packages/SettingsLib/HelpUtils/Android.bp
+++ b/packages/SettingsLib/HelpUtils/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibHelpUtils",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/IllustrationPreference/Android.bp b/packages/SettingsLib/IllustrationPreference/Android.bp
index 4d4759b..6407810 100644
--- a/packages/SettingsLib/IllustrationPreference/Android.bp
+++ b/packages/SettingsLib/IllustrationPreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibIllustrationPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/LayoutPreference/Android.bp b/packages/SettingsLib/LayoutPreference/Android.bp
index 53ded23..8cf636a 100644
--- a/packages/SettingsLib/LayoutPreference/Android.bp
+++ b/packages/SettingsLib/LayoutPreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibLayoutPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp
index 010a6ce..b984aaf 100644
--- a/packages/SettingsLib/MainSwitchPreference/Android.bp
+++ b/packages/SettingsLib/MainSwitchPreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibMainSwitchPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/ProfileSelector/Android.bp b/packages/SettingsLib/ProfileSelector/Android.bp
index 155ed2e..6dc07b2 100644
--- a/packages/SettingsLib/ProfileSelector/Android.bp
+++ b/packages/SettingsLib/ProfileSelector/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibProfileSelector",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/RestrictedLockUtils/Android.bp b/packages/SettingsLib/RestrictedLockUtils/Android.bp
index 3b04bd9..8d722eb 100644
--- a/packages/SettingsLib/RestrictedLockUtils/Android.bp
+++ b/packages/SettingsLib/RestrictedLockUtils/Android.bp
@@ -16,6 +16,9 @@
android_library {
name: "SettingsLibRestrictedLockUtils",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/SchedulesProvider/Android.bp b/packages/SettingsLib/SchedulesProvider/Android.bp
index 22e4e94..c0fc741 100644
--- a/packages/SettingsLib/SchedulesProvider/Android.bp
+++ b/packages/SettingsLib/SchedulesProvider/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibSchedulesProvider",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
diff --git a/packages/SettingsLib/SearchProvider/Android.bp b/packages/SettingsLib/SearchProvider/Android.bp
index c385d38..61ed65c 100644
--- a/packages/SettingsLib/SearchProvider/Android.bp
+++ b/packages/SettingsLib/SearchProvider/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibSearchProvider",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp b/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
index 702387e..2fe446d 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibSelectorWithWidgetPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/SettingsSpinner/Android.bp b/packages/SettingsLib/SettingsSpinner/Android.bp
index 0eec505..8fed61f 100644
--- a/packages/SettingsLib/SettingsSpinner/Android.bp
+++ b/packages/SettingsLib/SettingsSpinner/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibSettingsSpinner",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/SettingsTransition/Android.bp b/packages/SettingsLib/SettingsTransition/Android.bp
index 06493c0..e04af6c 100644
--- a/packages/SettingsLib/SettingsTransition/Android.bp
+++ b/packages/SettingsLib/SettingsTransition/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibSettingsTransition",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index ed90284..df5644b 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -74,8 +74,7 @@
android:exported="false">
</provider>
<activity
- android:name="com.android.settingslib.spa.gallery.SpaDialogActivity"
- android:excludeFromRecents="true"
+ android:name="com.android.settingslib.spa.gallery.GalleryDialogActivity"
android:exported="true"
android:theme="@style/Theme.SpaLib.Dialog">
</activity>
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDialogActivity.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDialogActivity.kt
new file mode 100644
index 0000000..e22ed35
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDialogActivity.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import com.android.settingslib.spa.SpaBaseDialogActivity
+import com.android.settingslib.spa.widget.dialog.AlertDialogButton
+import com.android.settingslib.spa.widget.dialog.SettingsAlertDialogWithIcon
+
+class GalleryDialogActivity : SpaBaseDialogActivity() {
+ @Composable
+ override fun Content() {
+ SettingsAlertDialogWithIcon(
+ onDismissRequest = { finish() },
+ confirmButton = AlertDialogButton("confirm") { finish() },
+ dismissButton = AlertDialogButton("dismiss") { finish() },
+ title = "title",
+ text = {
+ Text(
+ "text",
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center
+ )
+ }
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaDialogActivity.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaDialogActivity.kt
deleted file mode 100644
index 8b80fe2..0000000
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaDialogActivity.kt
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.gallery
-
-import android.os.Bundle
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.compose.foundation.layout.width
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.WarningAmber
-import androidx.compose.material3.AlertDialog
-import androidx.compose.material3.Button
-import androidx.compose.material3.Icon
-import androidx.compose.material3.OutlinedButton
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.vector.ImageVector
-import com.android.settingslib.spa.framework.common.LogCategory
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.widget.dialog.getDialogWidth
-
-
-class SpaDialogActivity : ComponentActivity() {
- private val spaEnvironment get() = SpaEnvironmentFactory.instance
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
- setContent {
- SettingsTheme {
- Content()
- }
- }
- }
-
- @Composable
- fun Content() {
- var openAlertDialog by remember { mutableStateOf(false) }
- AlertDialog(openAlertDialog)
- LaunchedEffect(key1 = Unit) {
- openAlertDialog = true
- }
- }
-
- @Composable
- fun AlertDialog(openAlertDialog: Boolean) {
- when {
- openAlertDialog -> {
- AlertDialogExample(
- onDismissRequest = { finish() },
- onConfirmation = { finish() },
- dialogTitle = intent.getStringExtra(DIALOG_TITLE) ?: DIALOG_TITLE,
- dialogText = intent.getStringExtra(DIALOG_TEXT) ?: DIALOG_TEXT,
- icon = Icons.Default.WarningAmber
- )
- }
- }
- }
-
- @Composable
- fun AlertDialogExample(
- onDismissRequest: () -> Unit,
- onConfirmation: () -> Unit,
- dialogTitle: String,
- dialogText: String,
- icon: ImageVector,
- ) {
- AlertDialog(
- modifier = Modifier.width(getDialogWidth()),
- icon = {
- Icon(icon, contentDescription = null)
- },
- title = {
- Text(text = dialogTitle)
- },
- text = {
- Text(text = dialogText)
- },
- onDismissRequest = {
- onDismissRequest()
- },
- dismissButton = {
- OutlinedButton(
- onClick = {
- onDismissRequest()
- }
- ) {
- Text(intent.getStringExtra(DISMISS_TEXT) ?: DISMISS_TEXT)
- }
- },
- confirmButton = {
- Button(
- onClick = {
- onConfirmation()
- },
- ) {
- Text(intent.getStringExtra(CONFIRM_TEXT) ?: CONFIRM_TEXT)
- }
- }
- )
- }
-
- companion object {
- private const val TAG = "SpaDialogActivity"
- private const val DIALOG_TITLE = "dialogTitle"
- private const val DIALOG_TEXT = "dialogText"
- private const val CONFIRM_TEXT = "confirmText"
- private const val DISMISS_TEXT = "dismissText"
- }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/SpaBaseDialogActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/SpaBaseDialogActivity.kt
new file mode 100644
index 0000000..dfb780a
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/SpaBaseDialogActivity.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.framework.common.LogCategory
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+
+abstract class SpaBaseDialogActivity : ComponentActivity() {
+ private val spaEnvironment get() = SpaEnvironmentFactory.instance
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
+ setContent {
+ SettingsTheme {
+ Content()
+ }
+ }
+ }
+
+ @Composable
+ abstract fun Content()
+
+ companion object {
+ private const val TAG = "SpaBaseDialogActivity"
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
index 8ffd799..de080e3 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
@@ -102,8 +102,8 @@
fun getDialogWidth(): Dp {
val configuration = LocalConfiguration.current
return configuration.screenWidthDp.dp * when (configuration.orientation) {
- Configuration.ORIENTATION_LANDSCAPE -> 0.6f
- else -> 0.8f
+ Configuration.ORIENTATION_LANDSCAPE -> 0.65f
+ else -> 0.85f
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt
new file mode 100644
index 0000000..1695e4f
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.dialog
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.WarningAmber
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Button
+import androidx.compose.material3.Icon
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.window.DialogProperties
+
+@Composable
+fun SettingsAlertDialogWithIcon(
+ onDismissRequest: () -> Unit,
+ confirmButton: AlertDialogButton?,
+ dismissButton: AlertDialogButton?,
+ title: String?,
+ text: @Composable (() -> Unit)?,
+) {
+ AlertDialog(
+ onDismissRequest = onDismissRequest,
+ icon = { Icon(Icons.Default.WarningAmber, contentDescription = null) },
+ modifier = Modifier.width(getDialogWidth()),
+ confirmButton = {
+ confirmButton?.let {
+ Button(
+ onClick = {
+ it.onClick()
+ },
+ ) {
+ Text(it.text)
+ }
+ }
+ },
+ dismissButton = dismissButton?.let {
+ {
+ OutlinedButton(
+ onClick = {
+ it.onClick()
+ },
+ ) {
+ Text(it.text)
+ }
+ }
+ },
+ title = title?.let {
+ {
+ Text(
+ it,
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center
+ )
+ }
+ },
+ text = text?.let {
+ {
+ Column(Modifier.verticalScroll(rememberScrollState())) {
+ text()
+ }
+ }
+ },
+ properties = DialogProperties(usePlatformDefaultWidth = false),
+ )
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Tile/Android.bp b/packages/SettingsLib/Tile/Android.bp
index 19c59dd..54b9748 100644
--- a/packages/SettingsLib/Tile/Android.bp
+++ b/packages/SettingsLib/Tile/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibTile",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
diff --git a/packages/SettingsLib/TopIntroPreference/Android.bp b/packages/SettingsLib/TopIntroPreference/Android.bp
index 77b7ac1..e70201b 100644
--- a/packages/SettingsLib/TopIntroPreference/Android.bp
+++ b/packages/SettingsLib/TopIntroPreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibTopIntroPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/TwoTargetPreference/Android.bp b/packages/SettingsLib/TwoTargetPreference/Android.bp
index 5aa906e..70d9630 100644
--- a/packages/SettingsLib/TwoTargetPreference/Android.bp
+++ b/packages/SettingsLib/TwoTargetPreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibTwoTargetPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/UsageProgressBarPreference/Android.bp b/packages/SettingsLib/UsageProgressBarPreference/Android.bp
index 4cc90cc..0a83aab 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/Android.bp
+++ b/packages/SettingsLib/UsageProgressBarPreference/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibUsageProgressBarPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/Utils/Android.bp b/packages/SettingsLib/Utils/Android.bp
index d5a56c8..5d2aef7 100644
--- a/packages/SettingsLib/Utils/Android.bp
+++ b/packages/SettingsLib/Utils/Android.bp
@@ -10,6 +10,9 @@
android_library {
name: "SettingsLibUtils",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/search/Android.bp b/packages/SettingsLib/search/Android.bp
index 390c9d2e..1f8d1dd 100644
--- a/packages/SettingsLib/search/Android.bp
+++ b/packages/SettingsLib/search/Android.bp
@@ -17,6 +17,9 @@
android_library {
name: "SettingsLib-search",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
static_libs: [
"SettingsLib-search-interface",
],
diff --git a/packages/SettingsProvider/TEST_MAPPING b/packages/SettingsProvider/TEST_MAPPING
index 890510f..0eed2b7 100644
--- a/packages/SettingsProvider/TEST_MAPPING
+++ b/packages/SettingsProvider/TEST_MAPPING
@@ -11,5 +11,10 @@
}
]
}
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsDeviceConfigTestCases"
+ }
]
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 5004f25..8ae50eb 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -241,6 +241,8 @@
Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO,
Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE,
Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME,
+ Settings.Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY,
+ Settings.Secure.BLUETOOTH_LE_BROADCAST_FALLBACK_ACTIVE_DEVICE_ADDRESS,
Settings.Secure.CUSTOM_BUGREPORT_HANDLER_APP,
Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER,
Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 0b0e182..285c8c9 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -387,6 +387,11 @@
VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_CODE, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME, ANY_STRING_VALIDATOR);
+ VALIDATORS.put(
+ Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY,
+ new DiscreteValueValidator(new String[] {"0", "1"}));
+ VALIDATORS.put(
+ Secure.BLUETOOTH_LE_BROADCAST_FALLBACK_ACTIVE_DEVICE_ADDRESS, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_APP, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_USER, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.LOCK_SCREEN_WEATHER_ENABLED, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index ecac5ee..edbc0b3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -14,3 +14,11 @@
bug: "311155098"
is_fixed_read_only: true
}
+
+flag {
+ name: "configurable_font_scale_default"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Whether the font_scale is read from a device dependent configuration file"
+ bug: "319808237"
+ is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 507d9c4..3dfc454 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -559,6 +559,9 @@
<!-- Permission required for CTS test - android.server.biometrics -->
<uses-permission android:name="android.permission.TEST_BIOMETRIC" />
+ <!-- Permission required for CTS test - android.server.biometrics -->
+ <uses-permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG" />
+
<!-- Permissions required for CTS test - NotificationManagerTest -->
<uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" />
@@ -891,6 +894,9 @@
<!-- Permission required for Cts test - CtsNotificationTestCases -->
<uses-permission android:name="android.permission.RECEIVE_SENSITIVE_NOTIFICATIONS" />
+ <!-- Permission required for BinaryTransparencyService shell API and host test -->
+ <uses-permission android:name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" />
+
<application
android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 7443e4c..168e6e0 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -355,6 +355,8 @@
<uses-permission android:name="android.permission.MONITOR_KEYBOARD_BACKLIGHT" />
+ <uses-permission android:name="android.permission.MONITOR_STICKY_MODIFIER_STATE" />
+
<!-- Listen to (dis-)connection of external displays and enable / disable them. -->
<uses-permission android:name="android.permission.MANAGE_DISPLAYS" />
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index c8e18d7..0a1a98f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -610,7 +610,7 @@
model.appWidgetHost.setInteractionHandler(viewModel.getInteractionHandler())
val view =
model.appWidgetHost
- .createView(context, model.appWidgetId, model.providerInfo)
+ .createViewForCommunal(context, model.appWidgetId, model.providerInfo)
.apply { updateAppWidgetSize(Bundle.EMPTY, listOf(size)) }
model.appWidgetHost.setInteractionHandler(null)
view
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 900616f..42fcd13 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -16,23 +16,55 @@
package com.android.systemui.keyguard.ui.composable.section
+import android.content.Context
+import android.view.ViewGroup
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.statusbar.notification.stack.AmbientState
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationStackAppearanceViewBinder
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import javax.inject.Inject
class NotificationSection
@Inject
constructor(
+ @Application context: Context,
private val viewModel: NotificationsPlaceholderViewModel,
+ controller: NotificationStackScrollLayoutController,
+ sceneContainerFlags: SceneContainerFlags,
+ sharedNotificationContainer: SharedNotificationContainer,
+ stackScrollLayout: NotificationStackScrollLayout,
+ notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel,
+ ambientState: AmbientState,
) {
+ init {
+ if (sceneContainerFlags.flexiNotifsEnabled()) {
+ (stackScrollLayout.parent as? ViewGroup)?.removeView(stackScrollLayout)
+ sharedNotificationContainer.addNotificationStackScrollLayout(stackScrollLayout)
+
+ NotificationStackAppearanceViewBinder.bind(
+ context,
+ sharedNotificationContainer,
+ notificationStackAppearanceViewModel,
+ ambientState,
+ controller,
+ )
+ }
+ }
+
@Composable
fun SceneScope.Notifications(modifier: Modifier = Modifier) {
NotificationStack(
viewModel = viewModel,
- isScrimVisible = false,
modifier = modifier,
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 0b26ae9..e835d3e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -22,6 +22,7 @@
import androidx.compose.foundation.layout.Box
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.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
@@ -43,8 +44,10 @@
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.height
import com.android.systemui.notifications.ui.composable.Notifications.Form
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
+import kotlin.math.roundToInt
object Notifications {
object Elements {
@@ -77,32 +80,52 @@
)
}
-/** Adds the space where notification stack will appear in the scene. */
+/** Adds the space where notification stack should appear in the scene. */
@Composable
fun SceneScope.NotificationStack(
viewModel: NotificationsPlaceholderViewModel,
- isScrimVisible: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ NotificationPlaceholder(
+ viewModel = viewModel,
+ form = Form.Stack,
+ modifier = modifier,
+ )
+}
+
+/**
+ * Adds the space where notification stack should appear in the scene, with a scrim and nested
+ * scrolling.
+ */
+@Composable
+fun SceneScope.NotificationScrollingStack(
+ viewModel: NotificationsPlaceholderViewModel,
modifier: Modifier = Modifier,
) {
val cornerRadius by viewModel.cornerRadiusDp.collectAsState()
- Box(modifier = modifier) {
- if (isScrimVisible) {
- Box(
- modifier =
- Modifier.element(Notifications.Elements.NotificationScrim)
- .fillMaxSize()
- .graphicsLayer {
- shape = RoundedCornerShape(cornerRadius.dp)
- clip = true
- }
- .background(MaterialTheme.colorScheme.surface)
- )
- }
+ val contentHeight by viewModel.intrinsicContentHeight.collectAsState()
+
+ val expansionFraction by viewModel.expandFraction.collectAsState(0f)
+
+ Box(
+ modifier =
+ modifier
+ .verticalNestedScrollToScene()
+ .fillMaxWidth()
+ .element(Notifications.Elements.NotificationScrim)
+ .graphicsLayer {
+ shape = RoundedCornerShape(cornerRadius.dp)
+ clip = true
+ alpha = expansionFraction
+ }
+ .background(MaterialTheme.colorScheme.surface)
+ .debugBackground(viewModel, Color(0.5f, 0.5f, 0f, 0.2f))
+ ) {
NotificationPlaceholder(
viewModel = viewModel,
form = Form.Stack,
- modifier = Modifier.fillMaxSize(),
+ modifier = Modifier.fillMaxWidth().height { contentHeight.roundToInt() }
)
}
}
@@ -159,6 +182,7 @@
debugLog(viewModel) { "STACK onSizeChanged: size=$size" }
}
.onPlaced { coordinates: LayoutCoordinates ->
+ viewModel.onContentTopChanged(coordinates.positionInWindow().y)
debugLog(viewModel) {
"STACK onPlaced:" +
" size=${coordinates.size}" +
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index bded98d..747faab 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -25,6 +25,7 @@
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace
+import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.Edge
import com.android.systemui.scene.shared.model.SceneKey
@@ -66,6 +67,7 @@
modifier: Modifier,
) {
Box(modifier = modifier) {
+ Box(modifier = Modifier.fillMaxSize().element(Notifications.Elements.NotificationScrim))
HeadsUpNotificationSpace(
viewModel = notificationsViewModel,
modifier = Modifier.padding(16.dp).fillMaxSize(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
index 6bb525a..0c2c519 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
@@ -3,12 +3,12 @@
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
-import com.android.systemui.notifications.ui.composable.Notifications
-import com.android.systemui.scene.ui.composable.Shade
+import com.android.systemui.qs.ui.composable.QuickSettings
+import com.android.systemui.shade.ui.composable.ShadeHeader
fun TransitionBuilder.goneToShadeTransition() {
spec = tween(durationMillis = 500)
- translate(Shade.rootElementKey, Edge.Top, true)
- fade(Notifications.Elements.NotificationScrim)
+ fractionRange(start = .58f) { fade(ShadeHeader.Elements.CollapsedContent) }
+ translate(QuickSettings.Elements.Content, Edge.Top, true)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 9c0f1fe..085dcf4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -22,11 +22,9 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
@@ -47,7 +45,7 @@
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
-import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.notifications.ui.composable.NotificationScrollingStack
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Direction
@@ -148,35 +146,27 @@
mediaHost: MediaHost,
modifier: Modifier = Modifier,
) {
+ val localDensity = LocalDensity.current
val layoutWidth = remember { mutableStateOf(0) }
- Box(modifier.element(Shade.Elements.Scrim)) {
- Spacer(
- modifier =
- Modifier.element(Shade.Elements.ScrimBackground)
- .fillMaxSize()
- .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim)
- )
+ Box(
+ modifier =
+ modifier.element(Shade.Elements.Scrim).background(MaterialTheme.colorScheme.scrim),
+ )
+ Box {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
- modifier =
- Modifier.fillMaxSize()
- .clickable(onClick = { viewModel.onContentClicked() })
- .padding(
- start = Shade.Dimensions.HorizontalPadding,
- end = Shade.Dimensions.HorizontalPadding,
- bottom = 48.dp
- )
+ modifier = Modifier.fillMaxWidth().clickable(onClick = { viewModel.onContentClicked() })
) {
CollapsedShadeHeader(
viewModel = viewModel.shadeHeaderViewModel,
createTintedIconManager = createTintedIconManager,
createBatteryMeterViewController = createBatteryMeterViewController,
statusBarIconController = statusBarIconController,
+ modifier = Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding)
)
- Spacer(modifier = Modifier.height(16.dp))
QuickSettings(
- modifier = Modifier.wrapContentHeight(),
+ modifier = Modifier.height(130.dp),
viewModel.qsSceneAdapter,
)
@@ -202,16 +192,15 @@
},
mediaHost = mediaHost,
layoutWidth = layoutWidth.value,
- layoutHeight = with(LocalDensity.current) { mediaHeight.toPx() }.toInt(),
+ layoutHeight = with(localDensity) { mediaHeight.toPx() }.toInt(),
carouselController = mediaCarouselController,
)
}
Spacer(modifier = Modifier.height(16.dp))
- NotificationStack(
+ NotificationScrollingStack(
viewModel = viewModel.notifications,
- isScrimVisible = true,
- modifier = Modifier.weight(1f),
+ modifier = Modifier.fillMaxWidth().weight(1f),
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index bc3ca1b..2a793ea 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -47,16 +47,20 @@
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.SessionTracker
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
@@ -66,6 +70,7 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.mockito.any
@@ -156,7 +161,7 @@
private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
private lateinit var keyguardPasswordView: KeyguardPasswordView
private lateinit var testableResources: TestableResources
- private lateinit var sceneTestUtils: SceneTestUtils
+ private lateinit var kosmos: Kosmos
private lateinit var sceneInteractor: SceneInteractor
private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
private lateinit var deviceEntryInteractor: DeviceEntryInteractor
@@ -222,15 +227,15 @@
mSelectedUserInteractor,
)
- sceneTestUtils = SceneTestUtils(this)
- sceneInteractor = sceneTestUtils.sceneInteractor()
+ kosmos = testKosmos()
+ sceneInteractor = kosmos.sceneInteractor
keyguardTransitionInteractor =
- KeyguardTransitionInteractorFactory.create(sceneTestUtils.testScope.backgroundScope)
+ KeyguardTransitionInteractorFactory.create(kosmos.testScope.backgroundScope)
.keyguardTransitionInteractor
sceneTransitionStateFlow =
MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
sceneInteractor.setTransitionState(sceneTransitionStateFlow)
- deviceEntryInteractor = sceneTestUtils.deviceEntryInteractor()
+ deviceEntryInteractor = kosmos.deviceEntryInteractor
mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
underTest =
@@ -249,7 +254,7 @@
falsingManager,
userSwitcherController,
featureFlags,
- sceneTestUtils.fakeSceneContainerFlags,
+ kosmos.fakeSceneContainerFlags,
globalSettings,
sessionTracker,
Optional.of(sideFpsController),
@@ -259,7 +264,7 @@
audioManager,
faceAuthInteractor,
mock(),
- { JavaAdapter(sceneTestUtils.testScope.backgroundScope) },
+ { JavaAdapter(kosmos.testScope.backgroundScope) },
mSelectedUserInteractor,
deviceProvisionedController,
faceAuthAccessibilityDelegate,
@@ -786,8 +791,8 @@
@Test
fun dismissesKeyguard_whenSceneChangesToGone() =
- sceneTestUtils.testScope.runTest {
- sceneTestUtils.fakeSceneContainerFlags.enabled = true
+ kosmos.testScope.runTest {
+ kosmos.fakeSceneContainerFlags.enabled = true
// Upon init, we have never dismisses the keyguard.
underTest.onInit()
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
index 27d1eb7..c86c747 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
@@ -17,14 +17,15 @@
package com.android.systemui.accessibility.data.repository
import android.os.UserHandle
+import android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -51,6 +52,7 @@
underTest =
ColorCorrectionRepositoryImpl(
testDispatcher,
+ scope.backgroundScope,
settings,
)
}
@@ -58,83 +60,78 @@
@Test
fun isEnabled_initiallyGetsSettingsValue() =
scope.runTest {
+ val actualValue by collectLastValue(underTest.isEnabled(testUser1))
+
settings.putIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
- 1,
+ SETTING_NAME,
+ ENABLED,
testUser1.identifier
)
-
- underTest =
- ColorCorrectionRepositoryImpl(
- testDispatcher,
- settings,
- )
-
- underTest.isEnabled(testUser1).launchIn(backgroundScope)
runCurrent()
- val actualValue: Boolean = underTest.isEnabled(testUser1).first()
Truth.assertThat(actualValue).isTrue()
}
@Test
fun isEnabled_settingUpdated_valueUpdated() =
scope.runTest {
- underTest.isEnabled(testUser1).launchIn(backgroundScope)
+ val flowValues: List<Boolean> by collectValues(underTest.isEnabled(testUser1))
settings.putIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
- ColorCorrectionRepositoryImpl.DISABLED,
+ SETTING_NAME,
+ DISABLED,
testUser1.identifier
)
runCurrent()
- Truth.assertThat(underTest.isEnabled(testUser1).first()).isFalse()
settings.putIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
- ColorCorrectionRepositoryImpl.ENABLED,
+ SETTING_NAME,
+ ENABLED,
testUser1.identifier
)
runCurrent()
- Truth.assertThat(underTest.isEnabled(testUser1).first()).isTrue()
settings.putIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
- ColorCorrectionRepositoryImpl.DISABLED,
+ SETTING_NAME,
+ DISABLED,
testUser1.identifier
)
runCurrent()
- Truth.assertThat(underTest.isEnabled(testUser1).first()).isFalse()
+
+ Truth.assertThat(flowValues.size).isEqualTo(3)
+ Truth.assertThat(flowValues).containsExactly(false, true, false).inOrder()
}
@Test
fun isEnabled_settingForUserOneOnly_valueUpdatedForUserOneOnly() =
scope.runTest {
- underTest.isEnabled(testUser1).launchIn(backgroundScope)
+ val lastValueUser1 by collectLastValue(underTest.isEnabled(testUser1))
+ val lastValueUser2 by collectLastValue(underTest.isEnabled(testUser2))
+
settings.putIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
- ColorCorrectionRepositoryImpl.DISABLED,
+ SETTING_NAME,
+ DISABLED,
testUser1.identifier
)
- underTest.isEnabled(testUser2).launchIn(backgroundScope)
settings.putIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
- ColorCorrectionRepositoryImpl.DISABLED,
+ SETTING_NAME,
+ DISABLED,
testUser2.identifier
)
-
runCurrent()
- Truth.assertThat(underTest.isEnabled(testUser1).first()).isFalse()
- Truth.assertThat(underTest.isEnabled(testUser2).first()).isFalse()
+
+ Truth.assertThat(lastValueUser1).isFalse()
+ Truth.assertThat(lastValueUser2).isFalse()
settings.putIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
- ColorCorrectionRepositoryImpl.ENABLED,
+ SETTING_NAME,
+ ENABLED,
testUser1.identifier
)
runCurrent()
- Truth.assertThat(underTest.isEnabled(testUser1).first()).isTrue()
- Truth.assertThat(underTest.isEnabled(testUser2).first()).isFalse()
+
+ Truth.assertThat(lastValueUser1).isTrue()
+ Truth.assertThat(lastValueUser2).isFalse()
}
@Test
@@ -146,10 +143,10 @@
val actualValue =
settings.getIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
+ SETTING_NAME,
testUser1.identifier
)
- Truth.assertThat(actualValue).isEqualTo(ColorCorrectionRepositoryImpl.ENABLED)
+ Truth.assertThat(actualValue).isEqualTo(ENABLED)
}
@Test
@@ -161,9 +158,15 @@
val actualValue =
settings.getIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
+ SETTING_NAME,
testUser1.identifier
)
- Truth.assertThat(actualValue).isEqualTo(ColorCorrectionRepositoryImpl.DISABLED)
+ Truth.assertThat(actualValue).isEqualTo(DISABLED)
}
+
+ companion object {
+ private const val SETTING_NAME = ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED
+ private const val DISABLED = 0
+ private const val ENABLED = 1
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
index 423e124..4853529 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
@@ -21,11 +21,11 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -52,6 +52,7 @@
underTest =
ColorInversionRepositoryImpl(
testDispatcher,
+ scope.backgroundScope,
settings,
)
}
@@ -59,55 +60,47 @@
@Test
fun isEnabled_initiallyGetsSettingsValue() =
scope.runTest {
- settings.putIntForUser(SETTING_NAME, 1, testUser1.identifier)
+ val actualValue by collectLastValue(underTest.isEnabled(testUser1))
- underTest =
- ColorInversionRepositoryImpl(
- testDispatcher,
- settings,
- )
-
- underTest.isEnabled(testUser1).launchIn(backgroundScope)
+ settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
runCurrent()
- val actualValue: Boolean = underTest.isEnabled(testUser1).first()
assertThat(actualValue).isTrue()
}
@Test
fun isEnabled_settingUpdated_valueUpdated() =
scope.runTest {
- underTest.isEnabled(testUser1).launchIn(backgroundScope)
+ val flowValues: List<Boolean> by
+ collectValues(underTest.isEnabled(testUser1))
settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
runCurrent()
- assertThat(underTest.isEnabled(testUser1).first()).isFalse()
-
settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
runCurrent()
- assertThat(underTest.isEnabled(testUser1).first()).isTrue()
-
settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
runCurrent()
- assertThat(underTest.isEnabled(testUser1).first()).isFalse()
+
+ assertThat(flowValues.size).isEqualTo(3)
+ assertThat(flowValues).containsExactly(false, true, false).inOrder()
}
@Test
fun isEnabled_settingForUserOneOnly_valueUpdatedForUserOneOnly() =
scope.runTest {
- underTest.isEnabled(testUser1).launchIn(backgroundScope)
- settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
- underTest.isEnabled(testUser2).launchIn(backgroundScope)
- settings.putIntForUser(SETTING_NAME, DISABLED, testUser2.identifier)
+ val lastValueUser1 by collectLastValue(underTest.isEnabled(testUser1))
+ val lastValueUser2 by collectLastValue(underTest.isEnabled(testUser2))
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser2.identifier)
runCurrent()
- assertThat(underTest.isEnabled(testUser1).first()).isFalse()
- assertThat(underTest.isEnabled(testUser2).first()).isFalse()
+ assertThat(lastValueUser1).isFalse()
+ assertThat(lastValueUser2).isFalse()
settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
runCurrent()
- assertThat(underTest.isEnabled(testUser1).first()).isTrue()
- assertThat(underTest.isEnabled(testUser2).first()).isFalse()
+ assertThat(lastValueUser1).isTrue()
+ assertThat(lastValueUser2).isFalse()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
index b4d4e1f..caf9219 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
@@ -29,10 +29,13 @@
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
@@ -59,8 +62,8 @@
@Mock private lateinit var tableLogger: TableLogBuffer
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
- private val testUtils = SceneTestUtils(this)
- private val testScope = testUtils.testScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private val clock = FakeSystemClock()
private val userRepository = FakeUserRepository()
private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
@@ -82,8 +85,8 @@
underTest =
AuthenticationRepositoryImpl(
applicationScope = testScope.backgroundScope,
- backgroundDispatcher = testUtils.testDispatcher,
- flags = testUtils.fakeSceneContainerFlags,
+ backgroundDispatcher = kosmos.testDispatcher,
+ flags = kosmos.sceneContainerFlags,
clock = clock,
getSecurityMode = getSecurityMode,
userRepository = userRepository,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index 10c16bd..cb8cebf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -22,6 +22,7 @@
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.None
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
@@ -29,7 +30,8 @@
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
import com.android.systemui.authentication.shared.model.AuthenticationWipeModel
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,9 +47,9 @@
@RunWith(AndroidJUnit4::class)
class AuthenticationInteractorTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val underTest = utils.authenticationInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.authenticationInteractor
private val onAuthenticationResult by
testScope.collectLastValue(underTest.onAuthenticationResult)
@@ -62,7 +64,7 @@
assertThat(authMethod).isEqualTo(Pin)
assertThat(underTest.getAuthenticationMethod()).isEqualTo(Pin)
- utils.authenticationRepository.setAuthenticationMethod(Password)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
assertThat(authMethod).isEqualTo(Password)
assertThat(underTest.getAuthenticationMethod()).isEqualTo(Password)
@@ -74,7 +76,7 @@
val authMethod by collectLastValue(underTest.authenticationMethod)
runCurrent()
- utils.authenticationRepository.setAuthenticationMethod(None)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(None)
assertThat(authMethod).isEqualTo(None)
assertThat(underTest.getAuthenticationMethod()).isEqualTo(None)
@@ -83,7 +85,7 @@
@Test
fun authenticate_withCorrectPin_succeeds() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
}
@@ -91,7 +93,7 @@
@Test
fun authenticate_withIncorrectPin_fails() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertFailed(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4)))
}
@@ -99,7 +101,7 @@
@Test(expected = IllegalArgumentException::class)
fun authenticate_withEmptyPin_throwsException() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
underTest.authenticate(listOf())
}
@@ -107,7 +109,7 @@
fun authenticate_withCorrectMaxLengthPin_succeeds() =
testScope.runTest {
val correctMaxLengthPin = List(16) { 9 }
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
overrideCredential(correctMaxLengthPin)
}
@@ -124,7 +126,7 @@
// If the policy changes, there is work to do in SysUI.
assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertFailed(underTest.authenticate(List(17) { 9 }))
}
@@ -132,7 +134,7 @@
@Test
fun authenticate_withCorrectPassword_succeeds() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Password)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
assertSucceeded(underTest.authenticate("password".toList()))
}
@@ -140,7 +142,7 @@
@Test
fun authenticate_withIncorrectPassword_fails() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Password)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
assertFailed(underTest.authenticate("alohomora".toList()))
}
@@ -148,7 +150,7 @@
@Test
fun authenticate_withCorrectPattern_succeeds() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Pattern)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pattern)
assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.PATTERN))
}
@@ -156,7 +158,7 @@
@Test
fun authenticate_withIncorrectPattern_fails() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Pattern)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pattern)
val wrongPattern =
listOf(
AuthenticationPatternCoordinate(x = 2, y = 0),
@@ -172,7 +174,7 @@
fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNull() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
}
@@ -182,14 +184,14 @@
assertSkipped(underTest.authenticate(shorterPin, tryAutoConfirm = true))
assertThat(underTest.lockoutEndTimestamp).isNull()
- assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
+ assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(0)
}
@Test
fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalse() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
}
@@ -207,7 +209,7 @@
fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalse() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
}
@@ -225,7 +227,7 @@
fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrue() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
}
@@ -241,7 +243,7 @@
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
reportLockoutStarted(42)
@@ -258,7 +260,7 @@
@Test
fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNull() =
testScope.runTest {
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(false)
}
@@ -271,7 +273,7 @@
@Test
fun tryAutoConfirm_withoutCorrectPassword_returnsNull() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Password)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
assertSkipped(underTest.authenticate("password".toList(), tryAutoConfirm = true))
}
@@ -280,7 +282,7 @@
fun isAutoConfirmEnabled_featureDisabled_returnsFalse() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(false)
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(false)
assertThat(isAutoConfirmEnabled).isFalse()
}
@@ -289,7 +291,7 @@
fun isAutoConfirmEnabled_featureEnabled_returnsTrue() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
assertThat(isAutoConfirmEnabled).isTrue()
}
@@ -298,7 +300,7 @@
fun isAutoConfirmEnabled_featureEnabledButDisabledByLockout() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
// The feature is enabled.
assertThat(isAutoConfirmEnabled).isTrue()
@@ -308,7 +310,7 @@
assertFailed(underTest.authenticate(listOf(5, 6, 7))) // Wrong PIN
}
assertThat(underTest.lockoutEndTimestamp).isNotNull()
- assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
+ assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(1)
// Lockout disabled auto-confirm.
assertThat(isAutoConfirmEnabled).isFalse()
@@ -336,7 +338,7 @@
val failedAuthenticationAttempts by
collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
assertSucceeded(underTest.authenticate(correctPin))
@@ -366,7 +368,7 @@
@Test
fun lockoutEndTimestamp() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
underTest.authenticate(correctPin)
@@ -384,7 +386,7 @@
val expectedLockoutEndTimestamp =
testScope.currentTime + FakeAuthenticationRepository.LOCKOUT_DURATION_MS
assertThat(underTest.lockoutEndTimestamp).isEqualTo(expectedLockoutEndTimestamp)
- assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
+ assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(1)
// Correct PIN, but locked out, so doesn't attempt it:
assertSkipped(underTest.authenticate(correctPin), assertNoResultEvents = false)
@@ -409,7 +411,7 @@
fun upcomingWipe() =
testScope.runTest {
val upcomingWipe by collectLastValue(underTest.upcomingWipe)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
val wrongPin = FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 }
@@ -418,7 +420,7 @@
var expectedFailedAttempts = 0
var remainingFailedAttempts =
- utils.authenticationRepository.getMaxFailedUnlockAttemptsForWipe()
+ kosmos.fakeAuthenticationRepository.getMaxFailedUnlockAttemptsForWipe()
assertThat(remainingFailedAttempts)
.isGreaterThan(LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE)
@@ -458,7 +460,7 @@
fun hintedPinLength_withoutAutoConfirm_isNull() =
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(false)
}
@@ -470,11 +472,13 @@
fun hintedPinLength_withAutoConfirmPinTooShort_isNull() =
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
overrideCredential(
buildList {
- repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) }
+ repeat(kosmos.fakeAuthenticationRepository.hintedPinLength - 1) {
+ add(it + 1)
+ }
}
)
setAutoConfirmFeatureEnabled(true)
@@ -487,28 +491,31 @@
fun hintedPinLength_withAutoConfirmPinAtRightLength_isSameLength() =
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
overrideCredential(
buildList {
- repeat(utils.authenticationRepository.hintedPinLength) { add(it + 1) }
+ repeat(kosmos.fakeAuthenticationRepository.hintedPinLength) { add(it + 1) }
}
)
}
- assertThat(hintedPinLength).isEqualTo(utils.authenticationRepository.hintedPinLength)
+ assertThat(hintedPinLength)
+ .isEqualTo(kosmos.fakeAuthenticationRepository.hintedPinLength)
}
@Test
fun hintedPinLength_withAutoConfirmPinTooLong_isNull() =
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
overrideCredential(
buildList {
- repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) }
+ repeat(kosmos.fakeAuthenticationRepository.hintedPinLength + 1) {
+ add(it + 1)
+ }
}
)
setAutoConfirmFeatureEnabled(true)
@@ -520,10 +527,10 @@
@Test
fun authenticate_withTooShortPassword() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Password)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
val tooShortPassword = buildList {
- repeat(utils.authenticationRepository.minPasswordLength - 1) { time ->
+ repeat(kosmos.fakeAuthenticationRepository.minPasswordLength - 1) { time ->
add("$time")
}
}
@@ -534,7 +541,7 @@
assertThat(authenticationResult).isEqualTo(AuthenticationResult.SUCCEEDED)
assertThat(onAuthenticationResult).isTrue()
assertThat(underTest.lockoutEndTimestamp).isNull()
- assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
+ assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(0)
assertThat(failedAuthenticationAttempts).isEqualTo(0)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
index 4a39799..72e884e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
@@ -16,6 +16,7 @@
package com.android.systemui.biometrics
+import android.graphics.Bitmap
import android.hardware.biometrics.BiometricManager.Authenticators
import android.hardware.biometrics.ComponentInfoInternal
import android.hardware.biometrics.PromptContentView
@@ -117,6 +118,8 @@
}
internal fun promptInfo(
+ logoRes: Int = -1,
+ logoBitmap: Bitmap? = null,
title: String = "title",
subtitle: String = "sub",
description: String = "desc",
@@ -127,6 +130,8 @@
negativeButton: String = "neg",
): PromptInfo {
val info = PromptInfo()
+ info.logoRes = logoRes
+ info.logoBitmap = logoBitmap
info.title = title
info.subtitle = subtitle
info.description = description
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
index c2117ae..a67b093 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
@@ -20,8 +20,11 @@
import androidx.test.filters.SmallTest
import com.android.internal.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
@@ -36,8 +39,8 @@
@RunWith(AndroidJUnit4::class)
class EmergencyServicesRepositoryImplTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var underTest: EmergencyServicesRepository
@@ -52,7 +55,7 @@
EmergencyServicesRepository(
resources = context.resources,
applicationScope = testScope.backgroundScope,
- configurationRepository = utils.configurationRepository,
+ configurationRepository = kosmos.configurationRepository,
)
}
@@ -71,7 +74,7 @@
private fun TestScope.setEmergencyCallWhileSimLocked(isEnabled: Boolean) {
overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, isEnabled)
- utils.configurationRepository.onConfigurationChange()
+ kosmos.fakeConfigurationRepository.onConfigurationChange()
runCurrent()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
index 63581b3..741cde8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
@@ -25,11 +25,18 @@
import com.android.internal.logging.nano.MetricsProto
import com.android.internal.util.emergencyAffordanceManager
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
+import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.whenever
import com.android.telecom.telecomManager
@@ -54,11 +61,11 @@
@Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
@Mock private lateinit var telecomManager: TelecomManager
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val metricsLogger = utils.kosmos.fakeMetricsLogger
- private val activityTaskManager = utils.kosmos.activityTaskManager
- private val emergencyAffordanceManager = utils.kosmos.emergencyAffordanceManager
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val metricsLogger = kosmos.fakeMetricsLogger
+ private val activityTaskManager = kosmos.activityTaskManager
+ private val emergencyAffordanceManager = kosmos.emergencyAffordanceManager
private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
@@ -68,9 +75,9 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- utils.fakeSceneContainerFlags.enabled = true
+ kosmos.fakeSceneContainerFlags.enabled = true
- mobileConnectionsRepository = utils.mobileConnectionsRepository
+ mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository
overrideResource(R.string.lockscreen_emergency_call, MESSAGE_EMERGENCY_CALL)
overrideResource(R.string.lockscreen_return_to_call, MESSAGE_RETURN_TO_CALL)
@@ -83,18 +90,18 @@
.thenReturn(needsEmergencyAffordance)
whenever(telecomManager.isInCall).thenReturn(false)
- utils.fakeFeatureFlags.set(REFACTOR_GETCURRENTUSER, true)
+ kosmos.fakeFeatureFlagsClassic.set(REFACTOR_GETCURRENTUSER, true)
- utils.telephonyRepository.setHasTelephonyRadio(true)
+ kosmos.fakeTelephonyRepository.setHasTelephonyRadio(true)
- utils.kosmos.telecomManager = telecomManager
+ kosmos.telecomManager = telecomManager
}
@Test
fun noTelephonyRadio_noButton() =
testScope.runTest {
- utils.telephonyRepository.setHasTelephonyRadio(false)
- val underTest = utils.bouncerActionButtonInteractor()
+ kosmos.fakeTelephonyRepository.setHasTelephonyRadio(false)
+ val underTest = kosmos.bouncerActionButtonInteractor
val actionButton by collectLastValue(underTest.actionButton)
assertThat(actionButton).isNull()
}
@@ -102,8 +109,8 @@
@Test
fun noTelecomManager_noButton() =
testScope.runTest {
- utils.kosmos.telecomManager = null
- val underTest = utils.bouncerActionButtonInteractor()
+ kosmos.telecomManager = null
+ val underTest = kosmos.bouncerActionButtonInteractor
val actionButton by collectLastValue(underTest.actionButton)
assertThat(actionButton).isNull()
}
@@ -111,9 +118,9 @@
@Test
fun duringCall_returnToCallButton() =
testScope.runTest {
- val underTest = utils.bouncerActionButtonInteractor()
+ val underTest = kosmos.bouncerActionButtonInteractor
val actionButton by collectLastValue(underTest.actionButton)
- utils.telephonyRepository.setIsInCall(true)
+ kosmos.fakeTelephonyRepository.setIsInCall(true)
assertThat(actionButton).isNotNull()
assertThat(actionButton?.label).isEqualTo(MESSAGE_RETURN_TO_CALL)
@@ -133,11 +140,13 @@
@Test
fun noCall_secureAuthMethod_emergencyCallButton() =
testScope.runTest {
- val underTest = utils.bouncerActionButtonInteractor()
+ val underTest = kosmos.bouncerActionButtonInteractor
val actionButton by collectLastValue(underTest.actionButton)
mobileConnectionsRepository.isAnySimSecure.value = false
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.telephonyRepository.setIsInCall(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeTelephonyRepository.setIsInCall(false)
assertThat(actionButton).isNotNull()
assertThat(actionButton?.label).isEqualTo(MESSAGE_EMERGENCY_CALL)
@@ -163,11 +172,13 @@
@Test
fun noCall_insecureAuthMethodButSecureSim_emergencyCallButton() =
testScope.runTest {
- val underTest = utils.bouncerActionButtonInteractor()
+ val underTest = kosmos.bouncerActionButtonInteractor
val actionButton by collectLastValue(underTest.actionButton)
mobileConnectionsRepository.isAnySimSecure.value = true
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- utils.telephonyRepository.setIsInCall(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ kosmos.fakeTelephonyRepository.setIsInCall(false)
runCurrent()
assertThat(actionButton).isNotNull()
@@ -179,11 +190,13 @@
@Test
fun noCall_insecure_noButton() =
testScope.runTest {
- val underTest = utils.bouncerActionButtonInteractor()
+ val underTest = kosmos.bouncerActionButtonInteractor
val actionButton by collectLastValue(underTest.actionButton)
mobileConnectionsRepository.isAnySimSecure.value = false
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- utils.telephonyRepository.setIsInCall(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ kosmos.fakeTelephonyRepository.setIsInCall(false)
assertThat(actionButton).isNull()
}
@@ -191,13 +204,15 @@
@Test
fun noCall_simSecureButEmergencyNotSupported_noButton() =
testScope.runTest {
- val underTest = utils.bouncerActionButtonInteractor()
+ val underTest = kosmos.bouncerActionButtonInteractor
val actionButton by collectLastValue(underTest.actionButton)
mobileConnectionsRepository.isAnySimSecure.value = true
overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, false)
- utils.configurationRepository.onConfigurationChange()
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- utils.telephonyRepository.setIsInCall(false)
+ kosmos.fakeConfigurationRepository.onConfigurationChange()
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ kosmos.fakeTelephonyRepository.setIsInCall(false)
runCurrent()
assertThat(actionButton).isNull()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 4b6199b..707777b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -20,15 +20,20 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.domain.interactor.AuthenticationResult
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,9 +50,9 @@
@RunWith(AndroidJUnit4::class)
class BouncerInteractorTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this).apply { fakeSceneContainerFlags.enabled = true }
- private val testScope = utils.testScope
- private val authenticationInteractor = utils.authenticationInteractor()
+ private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+ private val testScope = kosmos.testScope
+ private val authenticationInteractor = kosmos.authenticationInteractor
private lateinit var underTest: BouncerInteractor
@@ -62,7 +67,7 @@
overrideResource(R.string.kg_wrong_password, MESSAGE_WRONG_PASSWORD)
overrideResource(R.string.kg_wrong_pattern, MESSAGE_WRONG_PATTERN)
- underTest = utils.bouncerInteractor()
+ underTest = kosmos.bouncerInteractor
}
@Test
@@ -70,7 +75,9 @@
testScope.runTest {
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
runCurrent()
underTest.clearMessage()
assertThat(message).isNull()
@@ -94,7 +101,9 @@
@Test
fun pinAuthMethod_sim_skipsAuthentication() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Sim
+ )
runCurrent()
// We rely on TelephonyManager to authenticate the sim card.
@@ -109,9 +118,11 @@
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
runCurrent()
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
assertThat(isAutoConfirmEnabled).isTrue()
// Incomplete input.
@@ -137,7 +148,9 @@
testScope.runTest {
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
runCurrent()
// Incomplete input.
@@ -160,7 +173,7 @@
fun passwordAuthMethod() =
testScope.runTest {
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
runCurrent()
@@ -180,7 +193,8 @@
assertThat(
underTest.authenticate(
buildList {
- repeat(utils.authenticationRepository.minPasswordLength - 1) { time ->
+ repeat(kosmos.fakeAuthenticationRepository.minPasswordLength - 1) { time
+ ->
add("$time")
}
}
@@ -198,7 +212,7 @@
fun patternAuthMethod() =
testScope.runTest {
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pattern
)
runCurrent()
@@ -214,7 +228,8 @@
AuthenticationPatternCoordinate(0, 1),
)
assertThat(wrongPattern).isNotEqualTo(FakeAuthenticationRepository.PATTERN)
- assertThat(wrongPattern.size).isAtLeast(utils.authenticationRepository.minPatternLength)
+ assertThat(wrongPattern.size)
+ .isAtLeast(kosmos.fakeAuthenticationRepository.minPatternLength)
assertThat(underTest.authenticate(wrongPattern)).isEqualTo(AuthenticationResult.FAILED)
assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN)
@@ -225,7 +240,7 @@
val tooShortPattern =
FakeAuthenticationRepository.PATTERN.subList(
0,
- utils.authenticationRepository.minPatternLength - 1
+ kosmos.fakeAuthenticationRepository.minPatternLength - 1
)
assertThat(underTest.authenticate(tooShortPattern))
.isEqualTo(AuthenticationResult.SKIPPED)
@@ -245,7 +260,9 @@
val lockoutStartedEvents by collectValues(underTest.onLockoutStarted)
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
assertThat(lockoutStartedEvents).isEmpty()
// Try the wrong PIN repeatedly, until lockout is triggered:
@@ -291,17 +308,17 @@
@Test
fun intentionalUserInputEvent_registersTouchEvent() =
testScope.runTest {
- assertThat(utils.powerRepository.userTouchRegistered).isFalse()
+ assertThat(kosmos.fakePowerRepository.userTouchRegistered).isFalse()
underTest.onIntentionalUserInput()
- assertThat(utils.powerRepository.userTouchRegistered).isTrue()
+ assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue()
}
@Test
fun intentionalUserInputEvent_notifiesFaceAuthInteractor() =
testScope.runTest {
val isFaceAuthRunning by
- collectLastValue(utils.kosmos.fakeDeviceEntryFaceAuthRepository.isAuthRunning)
- utils.kosmos.deviceEntryFaceAuthInteractor.onDeviceLifted()
+ collectLastValue(kosmos.fakeDeviceEntryFaceAuthRepository.isAuthRunning)
+ kosmos.deviceEntryFaceAuthInteractor.onDeviceLifted()
runCurrent()
assertThat(isFaceAuthRunning).isTrue()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
index 8c53c0e..09fdd11 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
@@ -28,8 +28,11 @@
import com.android.systemui.bouncer.data.repository.FakeSimBouncerRepository
import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor.Companion.INVALID_SUBSCRIPTION_ID
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -54,10 +57,10 @@
@Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock lateinit var euiccManager: EuiccManager
- private val utils = SceneTestUtils(this)
+ private val kosmos = testKosmos()
private val bouncerSimRepository = FakeSimBouncerRepository()
private val resources: Resources = context.resources
- private val testScope = utils.testScope
+ private val testScope = kosmos.testScope
private lateinit var underTest: SimBouncerInteractor
@@ -68,13 +71,13 @@
SimBouncerInteractor(
context,
testScope.backgroundScope,
- utils.testDispatcher,
+ kosmos.testDispatcher,
bouncerSimRepository,
telephonyManager,
resources,
keyguardUpdateMonitor,
euiccManager,
- utils.mobileConnectionsRepository,
+ kosmos.mobileConnectionsRepository,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index 3043a71..27b84b2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -20,9 +20,13 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
@@ -33,16 +37,16 @@
@RunWith(AndroidJUnit4::class)
class AuthMethodBouncerViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val bouncerInteractor = utils.bouncerInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val bouncerInteractor = kosmos.bouncerInteractor
private val underTest =
PinBouncerViewModel(
applicationContext = context,
viewModelScope = testScope.backgroundScope,
interactor = bouncerInteractor,
isInputEnabled = MutableStateFlow(true),
- simBouncerInteractor = utils.simBouncerInteractor,
+ simBouncerInteractor = kosmos.simBouncerInteractor,
authenticationMethod = AuthenticationMethodModel.Pin,
)
@@ -50,7 +54,9 @@
fun animateFailure() =
testScope.runTest {
val animateFailure by collectLastValue(underTest.animateFailure)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
assertThat(animateFailure).isFalse()
// Wrong PIN:
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 4b1f9fe..cfe8c5d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -20,15 +20,21 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.None
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Sim
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlin.time.Duration.Companion.seconds
@@ -50,16 +56,16 @@
@RunWith(AndroidJUnit4::class)
class BouncerViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val authenticationInteractor = utils.authenticationInteractor()
- private val bouncerInteractor = utils.bouncerInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val authenticationInteractor = kosmos.authenticationInteractor
+ private val bouncerInteractor = kosmos.bouncerInteractor
private lateinit var underTest: BouncerViewModel
@Before
fun setUp() {
- utils.fakeSceneContainerFlags.enabled = true
- underTest = utils.bouncerViewModel()
+ kosmos.fakeSceneContainerFlags.enabled = true
+ underTest = kosmos.bouncerViewModel
}
@Test
@@ -68,7 +74,7 @@
var authMethodViewModel: AuthMethodBouncerViewModel? = null
authMethodsToTest().forEach { authMethod ->
- utils.authenticationRepository.setAuthenticationMethod(authMethod)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
val job =
underTest.authMethodViewModel.onEach { authMethodViewModel = it }.launchIn(this)
runCurrent()
@@ -98,13 +104,13 @@
// First pass, populate our "seen" map:
authMethodsToTest().forEach { authMethod ->
- utils.authenticationRepository.setAuthenticationMethod(authMethod)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
authMethodViewModel?.let { seen[authMethod] = it }
}
// Second pass, assert same instances are not reused:
authMethodsToTest().forEach { authMethod ->
- utils.authenticationRepository.setAuthenticationMethod(authMethod)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
authMethodViewModel?.let {
assertThat(it.authenticationMethod).isEqualTo(authMethod)
assertThat(it).isNotSameInstanceAs(seen[authMethod])
@@ -116,11 +122,11 @@
fun authMethodUnchanged_reusesInstances() =
testScope.runTest {
authMethodsToTest().forEach { authMethod ->
- utils.authenticationRepository.setAuthenticationMethod(authMethod)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
val firstInstance: AuthMethodBouncerViewModel? =
collectLastValue(underTest.authMethodViewModel).invoke()
- utils.authenticationRepository.setAuthenticationMethod(authMethod)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
val secondInstance: AuthMethodBouncerViewModel? =
collectLastValue(underTest.authMethodViewModel).invoke()
@@ -139,7 +145,7 @@
fun message() =
testScope.runTest {
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertThat(message?.isUpdateAnimated).isTrue()
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
@@ -157,8 +163,8 @@
testScope.runTest {
val authMethodViewModel by collectLastValue(underTest.authMethodViewModel)
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
- assertThat(utils.authenticationRepository.lockoutEndTimestamp).isNull()
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
+ assertThat(kosmos.fakeAuthenticationRepository.lockoutEndTimestamp).isNull()
assertThat(authMethodViewModel?.lockoutMessageId).isNotNull()
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { times ->
@@ -192,7 +198,7 @@
authViewModel?.isInputEnabled ?: emptyFlow()
}
)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertThat(isInputEnabled).isTrue()
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
@@ -210,7 +216,7 @@
testScope.runTest {
val authMethodViewModel by collectLastValue(underTest.authMethodViewModel)
val dialogViewModel by collectLastValue(underTest.dialogViewModel)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertThat(authMethodViewModel?.lockoutMessageId).isNotNull()
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
@@ -228,17 +234,17 @@
fun isSideBySideSupported() =
testScope.runTest {
val isSideBySideSupported by collectLastValue(underTest.isSideBySideSupported)
- utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertThat(isSideBySideSupported).isTrue()
- utils.authenticationRepository.setAuthenticationMethod(Password)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
assertThat(isSideBySideSupported).isTrue()
- utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertThat(isSideBySideSupported).isTrue()
- utils.authenticationRepository.setAuthenticationMethod(Password)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
assertThat(isSideBySideSupported).isFalse()
}
@@ -246,12 +252,12 @@
fun isFoldSplitRequired() =
testScope.runTest {
val isFoldSplitRequired by collectLastValue(underTest.isFoldSplitRequired)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertThat(isFoldSplitRequired).isTrue()
- utils.authenticationRepository.setAuthenticationMethod(Password)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
assertThat(isFoldSplitRequired).isFalse()
- utils.authenticationRepository.setAuthenticationMethod(Pattern)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pattern)
assertThat(isFoldSplitRequired).isTrue()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index 5c5632f..b3b6457 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -19,13 +19,19 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -43,12 +49,12 @@
@RunWith(AndroidJUnit4::class)
class PasswordBouncerViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val authenticationInteractor = utils.authenticationInteractor()
- private val sceneInteractor = utils.sceneInteractor()
- private val bouncerInteractor = utils.bouncerInteractor()
- private val bouncerViewModel = utils.bouncerViewModel()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val authenticationInteractor = kosmos.authenticationInteractor
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val bouncerInteractor = kosmos.bouncerInteractor
+ private val bouncerViewModel = kosmos.bouncerViewModel
private val isInputEnabled = MutableStateFlow(true)
private val underTest =
@@ -140,10 +146,10 @@
testScope.runTest {
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
- utils.authenticationRepository.setAuthenticationMethod(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
switchToScene(SceneKey.Bouncer)
// No input entered.
@@ -309,8 +315,10 @@
}
private fun TestScope.lockDeviceAndOpenPasswordBouncer() {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Password)
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Password
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
switchToScene(SceneKey.Bouncer)
}
@@ -320,13 +328,13 @@
) {
if (isLockedOut) {
repeat(failedAttemptCount) {
- utils.authenticationRepository.reportAuthenticationAttempt(false)
+ kosmos.fakeAuthenticationRepository.reportAuthenticationAttempt(false)
}
- utils.authenticationRepository.reportLockoutStarted(
+ kosmos.fakeAuthenticationRepository.reportLockoutStarted(
30.seconds.inWholeMilliseconds.toInt()
)
} else {
- utils.authenticationRepository.reportAuthenticationAttempt(true)
+ kosmos.fakeAuthenticationRepository.reportAuthenticationAttempt(true)
}
isInputEnabled.value = !isLockedOut
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 9ee344a..c2680bc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -20,13 +20,20 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.authenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -44,12 +51,12 @@
@RunWith(AndroidJUnit4::class)
class PatternBouncerViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val authenticationInteractor = utils.authenticationInteractor()
- private val sceneInteractor = utils.sceneInteractor()
- private val bouncerInteractor = utils.bouncerInteractor()
- private val bouncerViewModel = utils.bouncerViewModel()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val authenticationInteractor = kosmos.authenticationInteractor
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val bouncerInteractor = kosmos.bouncerInteractor
+ private val bouncerViewModel = kosmos.bouncerViewModel
private val underTest =
PatternBouncerViewModel(
applicationContext = context,
@@ -305,7 +312,7 @@
underTest.onDragStart()
CORRECT_PATTERN.subList(
0,
- utils.authenticationRepository.minPatternLength - 1,
+ kosmos.authenticationRepository.minPatternLength - 1,
)
.forEach { coordinate ->
underTest.onDrag(
@@ -374,8 +381,10 @@
}
private fun TestScope.lockDeviceAndOpenPatternBouncer() {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pattern)
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pattern
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
switchToScene(SceneKey.Bouncer)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 75e372f..1d660d6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -20,12 +20,20 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -43,19 +51,19 @@
@RunWith(AndroidJUnit4::class)
class PinBouncerViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val sceneInteractor = utils.sceneInteractor()
- private val authenticationInteractor = utils.authenticationInteractor()
- private val bouncerInteractor = utils.bouncerInteractor()
- private val bouncerViewModel = utils.bouncerViewModel()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val authenticationInteractor = kosmos.authenticationInteractor
+ private val bouncerInteractor = kosmos.bouncerInteractor
+ private val bouncerViewModel = kosmos.bouncerViewModel
private val underTest =
PinBouncerViewModel(
applicationContext = context,
viewModelScope = testScope.backgroundScope,
interactor = bouncerInteractor,
isInputEnabled = MutableStateFlow(true).asStateFlow(),
- simBouncerInteractor = utils.simBouncerInteractor,
+ simBouncerInteractor = kosmos.simBouncerInteractor,
authenticationMethod = AuthenticationMethodModel.Pin,
)
@@ -86,7 +94,7 @@
viewModelScope = testScope.backgroundScope,
interactor = bouncerInteractor,
isInputEnabled = MutableStateFlow(true).asStateFlow(),
- simBouncerInteractor = utils.simBouncerInteractor,
+ simBouncerInteractor = kosmos.simBouncerInteractor,
authenticationMethod = AuthenticationMethodModel.Sim,
)
@@ -97,7 +105,7 @@
fun onErrorDialogDismissed_clearsDialogMessage() =
testScope.runTest {
val dialogMessage by collectLastValue(underTest.errorDialogMessage)
- utils.simBouncerRepository.setSimVerificationErrorMessage("abc")
+ kosmos.fakeSimBouncerRepository.setSimVerificationErrorMessage("abc")
assertThat(dialogMessage).isEqualTo("abc")
underTest.onErrorDialogDismissed()
@@ -114,10 +122,10 @@
viewModelScope = testScope.backgroundScope,
interactor = bouncerInteractor,
isInputEnabled = MutableStateFlow(true).asStateFlow(),
- simBouncerInteractor = utils.simBouncerInteractor,
+ simBouncerInteractor = kosmos.simBouncerInteractor,
authenticationMethod = AuthenticationMethodModel.Sim,
)
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
assertThat(hintedPinLength).isNull()
@@ -254,7 +262,7 @@
@Test
fun onAutoConfirm_whenCorrect() =
testScope.runTest {
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
lockDeviceAndOpenPinBouncer()
@@ -269,7 +277,7 @@
val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
lockDeviceAndOpenPinBouncer()
FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit ->
@@ -309,7 +317,9 @@
testScope.runTest {
val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Shown)
}
@@ -318,8 +328,10 @@
fun backspaceButtonAppearance_withAutoConfirmButNoInput_isHidden() =
testScope.runTest {
val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
}
@@ -328,8 +340,10 @@
fun backspaceButtonAppearance_withAutoConfirmAndInput_isShownQuiet() =
testScope.runTest {
val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
underTest.onPinButtonClicked(1)
@@ -341,7 +355,9 @@
testScope.runTest {
val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Shown)
}
@@ -350,8 +366,10 @@
fun confirmButtonAppearance_withAutoConfirm_isHidden() =
testScope.runTest {
val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
}
@@ -361,10 +379,10 @@
testScope.runTest {
val isAnimationEnabled by collectLastValue(underTest.isDigitButtonAnimationEnabled)
- utils.authenticationRepository.setPinEnhancedPrivacyEnabled(true)
+ kosmos.fakeAuthenticationRepository.setPinEnhancedPrivacyEnabled(true)
assertThat(isAnimationEnabled).isFalse()
- utils.authenticationRepository.setPinEnhancedPrivacyEnabled(false)
+ kosmos.fakeAuthenticationRepository.setPinEnhancedPrivacyEnabled(false)
assertThat(isAnimationEnabled).isTrue()
}
@@ -382,8 +400,8 @@
}
private fun TestScope.lockDeviceAndOpenPinBouncer() {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
switchToScene(SceneKey.Bouncer)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
new file mode 100644
index 0000000..820bfbf
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.repository
+
+import android.content.SharedPreferences
+import android.content.pm.UserInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryImpl.Companion.FILE_NAME
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.FakeSharedPreferences
+import com.google.common.truth.Truth.assertThat
+import java.io.File
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
+ private lateinit var underTest: CommunalPrefsRepositoryImpl
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var userRepository: FakeUserRepository
+ private lateinit var userFileManager: UserFileManager
+
+ @Before
+ fun setUp() {
+ userRepository = kosmos.fakeUserRepository
+ userRepository.setUserInfos(USER_INFOS)
+
+ userFileManager =
+ FakeUserFileManager(
+ mapOf(
+ USER_INFOS[0].id to FakeSharedPreferences(),
+ USER_INFOS[1].id to FakeSharedPreferences()
+ )
+ )
+ underTest =
+ CommunalPrefsRepositoryImpl(
+ testScope.backgroundScope,
+ kosmos.testDispatcher,
+ userRepository,
+ userFileManager,
+ )
+ }
+
+ @Test
+ fun isCtaDismissedValue_byDefault_isFalse() =
+ testScope.runTest {
+ val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
+ assertThat(isCtaDismissed).isFalse()
+ }
+
+ @Test
+ fun isCtaDismissedValue_onSet_isTrue() =
+ testScope.runTest {
+ val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
+
+ underTest.setCtaDismissedForCurrentUser()
+ assertThat(isCtaDismissed).isTrue()
+ }
+
+ @Test
+ fun isCtaDismissedValue_whenSwitchUser() =
+ testScope.runTest {
+ val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
+ underTest.setCtaDismissedForCurrentUser()
+
+ // dismissed true for primary user
+ assertThat(isCtaDismissed).isTrue()
+
+ // switch to secondary user
+ userRepository.setSelectedUserInfo(USER_INFOS[1])
+
+ // dismissed is false for secondary user
+ assertThat(isCtaDismissed).isFalse()
+
+ // switch back to primary user
+ userRepository.setSelectedUserInfo(USER_INFOS[0])
+
+ // dismissed is true for primary user
+ assertThat(isCtaDismissed).isTrue()
+ }
+
+ private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) :
+ UserFileManager {
+ override fun getFile(fileName: String, userId: Int): File {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getSharedPreferences(
+ fileName: String,
+ mode: Int,
+ userId: Int
+ ): SharedPreferences {
+ if (fileName != FILE_NAME) {
+ throw IllegalArgumentException("Preference files must be $FILE_NAME")
+ }
+ return sharedPrefs.getValue(userId)
+ }
+ }
+
+ companion object {
+ val USER_INFOS =
+ listOf(
+ UserInfo(/* id= */ 0, "zero", /* flags= */ 0),
+ UserInfo(/* id= */ 1, "secondary", /* flags= */ 0),
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
index 65176e1..81d5344 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
@@ -24,11 +24,12 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
-import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.data.repository.SceneContainerRepository
+import com.android.systemui.scene.data.repository.sceneContainerRepository
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -51,8 +52,8 @@
@Before
fun setUp() {
- val sceneTestUtils = SceneTestUtils(this)
- sceneContainerRepository = sceneTestUtils.fakeSceneContainerRepository()
+ val kosmos = testKosmos()
+ sceneContainerRepository = kosmos.sceneContainerRepository
featureFlagsClassic = FakeFeatureFlagsClassic()
featureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 4079f12..1c6cecd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.communal.data.repository
-import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
@@ -32,6 +31,7 @@
import com.android.systemui.communal.data.db.CommunalWidgetItem
import com.android.systemui.communal.shared.CommunalWidgetHost
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.FakeLogBuffer
@@ -65,7 +65,7 @@
@Mock private lateinit var appWidgetManager: AppWidgetManager
- @Mock private lateinit var appWidgetHost: AppWidgetHost
+ @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
@Mock private lateinit var userManager: UserManager
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index cd83c07..178eb6a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -24,6 +24,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
@@ -65,6 +66,7 @@
private lateinit var widgetRepository: FakeCommunalWidgetRepository
private lateinit var smartspaceRepository: FakeSmartspaceRepository
private lateinit var keyguardRepository: FakeKeyguardRepository
+ private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository
private lateinit var editWidgetsActivityStarter: EditWidgetsActivityStarter
private lateinit var underTest: CommunalInteractor
@@ -84,6 +86,7 @@
smartspaceRepository = withDeps.smartspaceRepository
keyguardRepository = withDeps.keyguardRepository
editWidgetsActivityStarter = withDeps.editWidgetsActivityStarter
+ communalPrefsRepository = withDeps.communalPrefsRepository
underTest = withDeps.communalInteractor
}
@@ -331,10 +334,9 @@
}
@Test
- fun cta_visibilityTrue_shows() =
+ fun ctaTile_showsByDefault() =
testScope.runTest {
tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
- communalRepository.setCtaTileInViewModeVisibility(true)
val ctaTileContent by collectLastValue(underTest.ctaTileContent)
@@ -346,10 +348,10 @@
}
@Test
- fun ctaTile_visibilityFalse_doesNotShow() =
+ fun ctaTile_afterDismiss_doesNotShow() =
testScope.runTest {
tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
- communalRepository.setCtaTileInViewModeVisibility(false)
+ communalPrefsRepository.setCtaDismissedForCurrentUser()
val ctaTileContent by collectLastValue(underTest.ctaTileContent)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
new file mode 100644
index 0000000..e904236
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.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.systemui.communal.ui.widgets
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
+import com.android.systemui.communal.widgets.CommunalAppWidgetHostView
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalAppWidgetHostTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var underTest: CommunalAppWidgetHost
+
+ @Before
+ fun setUp() {
+ underTest = CommunalAppWidgetHost(context = context, hostId = 116)
+ }
+
+ @Test
+ fun createViewForCommunal_returnCommunalAppWidgetView() =
+ testScope.runTest {
+ val appWidgetId = 789
+ val view =
+ underTest.createViewForCommunal(
+ context = context,
+ appWidgetId = appWidgetId,
+ appWidget = null
+ )
+ assertThat(view).isInstanceOf(CommunalAppWidgetHostView::class.java)
+ assertThat(view).isNotNull()
+ assertThat(view.appWidgetId).isEqualTo(appWidgetId)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 54510a8..09243e5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -19,7 +19,6 @@
import android.app.Activity.RESULT_CANCELED
import android.app.Activity.RESULT_OK
import android.app.smartspace.SmartspaceTarget
-import android.appwidget.AppWidgetHost
import android.content.ComponentName
import android.provider.Settings
import android.widget.RemoteViews
@@ -36,6 +35,7 @@
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.kosmos.testScope
@@ -61,7 +61,7 @@
@RunWith(AndroidJUnit4::class)
class CommunalEditModeViewModelTest : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
- @Mock private lateinit var appWidgetHost: AppWidgetHost
+ @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
@Mock private lateinit var uiEventLogger: UiEventLogger
private val kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 804c052..f9cfc37 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -23,6 +23,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
@@ -66,6 +67,7 @@
private lateinit var widgetRepository: FakeCommunalWidgetRepository
private lateinit var smartspaceRepository: FakeSmartspaceRepository
private lateinit var mediaRepository: FakeCommunalMediaRepository
+ private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository
private lateinit var underTest: CommunalViewModel
@@ -82,6 +84,7 @@
widgetRepository = withDeps.widgetRepository
smartspaceRepository = withDeps.smartspaceRepository
mediaRepository = withDeps.mediaRepository
+ communalPrefsRepository = withDeps.communalPrefsRepository
underTest =
CommunalViewModel(
@@ -149,9 +152,6 @@
// Media playing.
mediaRepository.mediaActive()
- // CTA Tile not dismissed.
- communalRepository.setCtaTileInViewModeVisibility(true)
-
val communalContent by collectLastValue(underTest.communalContent)
// Order is smart space, then UMO, widget content and cta tile.
@@ -171,7 +171,6 @@
fun dismissCta_hidesCtaTileAndShowsPopup_thenHidesPopupAfterTimeout() =
testScope.runTest {
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
- communalRepository.setCtaTileInViewModeVisibility(true)
val communalContent by collectLastValue(underTest.communalContent)
val isPopupOnDismissCtaShowing by collectLastValue(underTest.isPopupOnDismissCtaShowing)
@@ -195,7 +194,6 @@
fun popup_onDismiss_hidesImmediately() =
testScope.runTest {
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
- communalRepository.setCtaTileInViewModeVisibility(true)
val isPopupOnDismissCtaShowing by collectLastValue(underTest.isPopupOnDismissCtaShowing)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
index 565049b..b54c5bd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
@@ -7,9 +7,11 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
@@ -36,8 +38,8 @@
@Mock private lateinit var keyguardBypassController: KeyguardBypassController
@Mock private lateinit var keyguardStateController: KeyguardStateController
- private val testUtils = SceneTestUtils(this)
- private val testScope = testUtils.testScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private val userRepository = FakeUserRepository()
private val keyguardRepository = FakeKeyguardRepository()
@@ -52,7 +54,7 @@
underTest =
DeviceEntryRepositoryImpl(
applicationScope = testScope.backgroundScope,
- backgroundDispatcher = testUtils.testDispatcher,
+ backgroundDispatcher = kosmos.testDispatcher,
userRepository = userRepository,
lockPatternUtils = lockPatternUtils,
keyguardBypassController = keyguardBypassController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 929e879..62d2315 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -20,14 +20,20 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeTrustRepository
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
@@ -41,18 +47,18 @@
@RunWith(AndroidJUnit4::class)
class DeviceEntryInteractorTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val faceAuthRepository = utils.kosmos.fakeDeviceEntryFaceAuthRepository
- private val trustRepository = utils.kosmos.fakeTrustRepository
- private val sceneInteractor = utils.sceneInteractor()
- private val authenticationInteractor = utils.authenticationInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
+ private val trustRepository = kosmos.fakeTrustRepository
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val authenticationInteractor = kosmos.authenticationInteractor
private lateinit var underTest: DeviceEntryInteractor
@Before
fun setUp() {
- utils.fakeSceneContainerFlags.enabled = true
- underTest = utils.deviceEntryInteractor()
+ kosmos.fakeSceneContainerFlags.enabled = true
+ underTest = kosmos.deviceEntryInteractor
}
@Test
@@ -65,8 +71,10 @@
@Test
fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- utils.deviceEntryRepository.apply {
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ kosmos.fakeDeviceEntryRepository.apply {
setLockscreenEnabled(false)
// Toggle isUnlocked, twice.
@@ -99,8 +107,10 @@
@Test
fun isUnlocked_whenAuthMethodIsSimAndUnlocked_isFalse() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim)
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Sim
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
val isUnlocked by collectLastValue(underTest.isUnlocked)
assertThat(isUnlocked).isFalse()
@@ -157,10 +167,10 @@
@Test
fun isDeviceEntered_onBouncer_isFalse() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pattern
)
- utils.deviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
switchToScene(SceneKey.Lockscreen)
runCurrent()
switchToScene(SceneKey.Bouncer)
@@ -182,8 +192,10 @@
@Test
fun canSwipeToEnter_onLockscreenWithPin_isFalse() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
switchToScene(SceneKey.Lockscreen)
val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
@@ -203,15 +215,15 @@
}
private fun setupSwipeDeviceEntryMethod() {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- utils.deviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
}
@Test
fun canSwipeToEnter_whenTrustedByTrustManager_isTrue() =
testScope.runTest {
val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
- utils.authenticationRepository.setAuthenticationMethod(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
switchToScene(SceneKey.Lockscreen)
@@ -228,7 +240,7 @@
fun canSwipeToEnter_whenAuthenticatedByFace_isTrue() =
testScope.runTest {
val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
- utils.authenticationRepository.setAuthenticationMethod(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
switchToScene(SceneKey.Lockscreen)
@@ -244,9 +256,9 @@
@Test
fun isAuthenticationRequired_lockedAndSecured_true() =
testScope.runTest {
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
runCurrent()
- utils.authenticationRepository.setAuthenticationMethod(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
@@ -256,9 +268,11 @@
@Test
fun isAuthenticationRequired_lockedAndNotSecured_false() =
testScope.runTest {
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
runCurrent()
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
assertThat(underTest.isAuthenticationRequired()).isFalse()
}
@@ -266,9 +280,9 @@
@Test
fun isAuthenticationRequired_unlockedAndSecured_false() =
testScope.runTest {
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
runCurrent()
- utils.authenticationRepository.setAuthenticationMethod(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
@@ -278,9 +292,11 @@
@Test
fun isAuthenticationRequired_unlockedAndNotSecured_false() =
testScope.runTest {
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
runCurrent()
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
assertThat(underTest.isAuthenticationRequired()).isFalse()
}
@@ -288,7 +304,7 @@
@Test
fun isBypassEnabled_enabledInRepository_true() =
testScope.runTest {
- utils.deviceEntryRepository.setBypassEnabled(true)
+ kosmos.fakeDeviceEntryRepository.setBypassEnabled(true)
assertThat(underTest.isBypassEnabled.value).isTrue()
}
@@ -299,8 +315,10 @@
switchToScene(SceneKey.Lockscreen)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
runCurrent()
underTest.attemptDeviceEntry()
@@ -315,7 +333,9 @@
switchToScene(SceneKey.Lockscreen)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
underTest.attemptDeviceEntry()
@@ -329,8 +349,10 @@
switchToScene(SceneKey.Lockscreen)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
- utils.deviceEntryRepository.setLockscreenEnabled(true)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
underTest.attemptDeviceEntry()
@@ -340,7 +362,7 @@
@Test
fun isBypassEnabled_disabledInRepository_false() =
testScope.runTest {
- utils.deviceEntryRepository.setBypassEnabled(false)
+ kosmos.fakeDeviceEntryRepository.setBypassEnabled(false)
assertThat(underTest.isBypassEnabled.value).isFalse()
}
@@ -348,8 +370,10 @@
fun successfulAuthenticationChallengeAttempt_updatesIsUnlockedState() =
testScope.runTest {
val isUnlocked by collectLastValue(underTest.isUnlocked)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
assertThat(isUnlocked).isFalse()
authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 8933d2c..2c3afb1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -26,12 +26,16 @@
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
+import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -48,10 +52,10 @@
@RunWith(AndroidJUnit4::class)
class KeyguardInteractorTest : SysuiTestCase() {
- private val testUtils = SceneTestUtils(this)
- private val testScope = testUtils.testScope
- private val repository = testUtils.keyguardRepository
- private val sceneInteractor = testUtils.sceneInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val repository = kosmos.fakeKeyguardRepository
+ private val sceneInteractor = kosmos.sceneInteractor
private val commandQueue = FakeCommandQueue()
private val bouncerRepository = FakeKeyguardBouncerRepository()
private val shadeRepository = FakeShadeRepository()
@@ -63,7 +67,7 @@
repository = repository,
commandQueue = commandQueue,
powerInteractor = PowerInteractorFactory.create().powerInteractor,
- sceneContainerFlags = testUtils.fakeSceneContainerFlags,
+ sceneContainerFlags = kosmos.fakeSceneContainerFlags,
bouncerRepository = bouncerRepository,
configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
shadeRepository = shadeRepository,
@@ -183,7 +187,7 @@
@Test
fun animationDozingTransitions() =
testScope.runTest {
- testUtils.fakeSceneContainerFlags.enabled = true
+ kosmos.fakeSceneContainerFlags.enabled = true
val isAnimate by collectLastValue(underTest.animateDozingTransitions)
underTest.setAnimateDozingTransitions(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 04e90c8..aa15d0b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -21,11 +21,19 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -37,9 +45,9 @@
@RunWith(AndroidJUnit4::class)
class LockscreenSceneViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val sceneInteractor = utils.sceneInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
private val underTest = createLockscreenSceneViewModel()
@@ -47,9 +55,11 @@
fun upTransitionSceneKey_canSwipeToUnlock_gone() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- utils.deviceEntryRepository.setLockscreenEnabled(true)
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
@@ -59,8 +69,10 @@
fun upTransitionSceneKey_cannotSwipeToUnlock_bouncer() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
@@ -69,7 +81,7 @@
@Test
fun leftTransitionSceneKey_communalIsEnabled_communal() =
testScope.runTest {
- utils.communalRepository.setIsCommunalEnabled(true)
+ kosmos.fakeCommunalRepository.setIsCommunalEnabled(true)
val underTest = createLockscreenSceneViewModel()
assertThat(underTest.leftDestinationSceneKey).isEqualTo(SceneKey.Communal)
@@ -78,7 +90,7 @@
@Test
fun leftTransitionSceneKey_communalIsDisabled_null() =
testScope.runTest {
- utils.communalRepository.setIsCommunalEnabled(false)
+ kosmos.fakeCommunalRepository.setIsCommunalEnabled(false)
val underTest = createLockscreenSceneViewModel()
assertThat(underTest.leftDestinationSceneKey).isNull()
@@ -87,13 +99,13 @@
private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
return LockscreenSceneViewModel(
applicationScope = testScope.backgroundScope,
- deviceEntryInteractor = utils.deviceEntryInteractor(),
- communalInteractor = utils.communalInteractor(),
+ deviceEntryInteractor = kosmos.deviceEntryInteractor,
+ communalInteractor = kosmos.communalInteractor,
longPress =
KeyguardLongPressViewModel(
interactor = mock(),
),
- notifications = utils.notificationsPlaceholderViewModel(),
+ notifications = kosmos.notificationsPlaceholderViewModel,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 6403ed1..be523b8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -22,13 +22,15 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.scene.shared.model.UserAction
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
@@ -36,6 +38,7 @@
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
@@ -47,9 +50,9 @@
@RunWith(AndroidJUnit4::class)
class QuickSettingsSceneViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val sceneInteractor = utils.sceneInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
private val qsFlexiglassAdapter = FakeQSSceneAdapter { mock() }
@@ -90,7 +93,7 @@
QuickSettingsSceneViewModel(
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
- notifications = utils.notificationsPlaceholderViewModel(),
+ notifications = kosmos.notificationsPlaceholderViewModel,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index ecc2ef1..a08283d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -27,22 +27,39 @@
import com.android.internal.util.emergencyAffordanceManager
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
+import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel
+import com.android.systemui.classifier.domain.interactor.falsingInteractor
+import com.android.systemui.classifier.falsingCollector
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.model.SysUiState
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.startable.SceneContainerStartable
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
@@ -50,13 +67,17 @@
import com.android.systemui.settings.FakeDisplayTracker
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -101,13 +122,13 @@
@RunWith(AndroidJUnit4::class)
class SceneFrameworkIntegrationTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this).apply { fakeSceneContainerFlags.enabled = true }
- private val testScope = utils.testScope
- private val sceneContainerConfig = utils.fakeSceneContainerConfig()
- private val sceneInteractor = utils.sceneInteractor()
- private val authenticationInteractor = utils.authenticationInteractor()
- private val deviceEntryInteractor = utils.deviceEntryInteractor()
- private val communalInteractor = utils.communalInteractor()
+ private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+ private val testScope = kosmos.testScope
+ private val sceneContainerConfig = kosmos.sceneContainerConfig
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val authenticationInteractor = kosmos.authenticationInteractor
+ private val deviceEntryInteractor = kosmos.deviceEntryInteractor
+ private val communalInteractor = kosmos.communalInteractor
private val transitionState =
MutableStateFlow<ObservableTransitionState>(
@@ -116,11 +137,11 @@
private val sceneContainerViewModel =
SceneContainerViewModel(
sceneInteractor = sceneInteractor,
- falsingInteractor = utils.falsingInteractor(),
+ falsingInteractor = kosmos.falsingInteractor,
)
.apply { setTransitionState(transitionState) }
- private val bouncerInteractor = utils.bouncerInteractor()
+ private val bouncerInteractor = kosmos.bouncerInteractor
private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
private lateinit var bouncerActionButtonInteractor: BouncerActionButtonInteractor
@@ -135,7 +156,7 @@
KeyguardLongPressViewModel(
interactor = mock(),
),
- notifications = utils.notificationsPlaceholderViewModel(),
+ notifications = kosmos.notificationsPlaceholderViewModel,
)
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
@@ -152,21 +173,22 @@
FakeMobileConnectionsRepository(),
),
constants = mock(),
- utils.fakeFeatureFlags,
+ flags = kosmos.fakeFeatureFlagsClassic,
scope = testScope.backgroundScope,
)
private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel
private lateinit var shadeSceneViewModel: ShadeSceneViewModel
- private val keyguardInteractor = utils.keyguardInteractor()
- private val powerInteractor = utils.powerInteractor()
+ private val keyguardInteractor = kosmos.keyguardInteractor
+ private val powerInteractor = kosmos.powerInteractor
private var bouncerSceneJob: Job? = null
private val qsFlexiglassAdapter = FakeQSSceneAdapter(inflateDelegate = { mock() })
@Mock private lateinit var mediaDataManager: MediaDataManager
+
@Mock private lateinit var mediaHost: MediaHost
private lateinit var emergencyAffordanceManager: EmergencyAffordanceManager
@@ -177,27 +199,27 @@
MockitoAnnotations.initMocks(this)
overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, true)
- telecomManager = checkNotNull(utils.kosmos.telecomManager)
+ telecomManager = checkNotNull(kosmos.telecomManager)
whenever(telecomManager.isInCall).thenReturn(false)
- emergencyAffordanceManager = utils.kosmos.emergencyAffordanceManager
+ emergencyAffordanceManager = kosmos.emergencyAffordanceManager
whenever(emergencyAffordanceManager.needsEmergencyAffordance()).thenReturn(true)
- utils.fakeFeatureFlags.apply {
+ kosmos.fakeFeatureFlagsClassic.apply {
set(Flags.NEW_NETWORK_SLICE_UI, false)
set(Flags.REFACTOR_GETCURRENTUSER, true)
}
- mobileConnectionsRepository = utils.mobileConnectionsRepository
+ mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository
mobileConnectionsRepository.isAnySimSecure.value = false
- utils.telephonyRepository.apply {
+ kosmos.fakeTelephonyRepository.apply {
setHasTelephonyRadio(true)
setCallState(TelephonyManager.CALL_STATE_IDLE)
setIsInCall(false)
}
- bouncerActionButtonInteractor = utils.bouncerActionButtonInteractor()
- bouncerViewModel = utils.bouncerViewModel()
+ bouncerActionButtonInteractor = kosmos.bouncerActionButtonInteractor
+ bouncerViewModel = kosmos.bouncerViewModel
shadeHeaderViewModel =
ShadeHeaderViewModel(
@@ -215,12 +237,12 @@
deviceEntryInteractor = deviceEntryInteractor,
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
- notifications = utils.notificationsPlaceholderViewModel(),
+ notifications = kosmos.notificationsPlaceholderViewModel,
mediaDataManager = mediaDataManager,
mediaHost = mediaHost,
)
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
val displayTracker = FakeDisplayTracker(context)
val sysUiState = SysUiState(displayTracker)
@@ -230,15 +252,15 @@
sceneInteractor = sceneInteractor,
deviceEntryInteractor = deviceEntryInteractor,
keyguardInteractor = keyguardInteractor,
- flags = utils.fakeSceneContainerFlags,
+ flags = kosmos.fakeSceneContainerFlags,
sysUiState = sysUiState,
displayId = displayTracker.defaultDisplayId,
sceneLogger = mock(),
- falsingCollector = utils.falsingCollector(),
+ falsingCollector = kosmos.falsingCollector,
powerInteractor = powerInteractor,
bouncerInteractor = bouncerInteractor,
- simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor },
- authenticationInteractor = dagger.Lazy { utils.authenticationInteractor() },
+ simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
+ authenticationInteractor = dagger.Lazy { kosmos.authenticationInteractor },
windowController = mock(),
)
startable.start()
@@ -534,15 +556,15 @@
// Set the lockscreen enabled bit _before_ set the auth method as the code picks up on the
// lockscreen enabled bit _after_ the auth method is changed and the lockscreen enabled bit
// is not an observable that can trigger a new evaluation.
- utils.deviceEntryRepository.setLockscreenEnabled(enableLockscreen)
- utils.authenticationRepository.setAuthenticationMethod(authMethod)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(enableLockscreen)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
runCurrent()
}
/** Emulates a phone call in progress. */
private fun TestScope.startPhoneCall() {
whenever(telecomManager.isInCall).thenReturn(true)
- utils.telephonyRepository.apply {
+ kosmos.fakeTelephonyRepository.apply {
setHasTelephonyRadio(true)
setIsInCall(true)
setCallState(TelephonyManager.CALL_STATE_OFFHOOK)
@@ -651,7 +673,7 @@
.that(authMethod.isSecure)
.isTrue()
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
runCurrent()
}
@@ -665,7 +687,7 @@
enterPin()
// This repository state is not changed by the AuthInteractor, it relies on
// KeyguardStateController.
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
emulateUiSceneTransition(
expectedVisible = false,
)
@@ -721,7 +743,7 @@
}
pinBouncerViewModel.onAuthenticateButtonClicked()
setAuthMethod(authMethodAfterSimUnlock)
- utils.mobileConnectionsRepository.isAnySimSecure.value = false
+ kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false
runCurrent()
}
@@ -768,7 +790,7 @@
private fun TestScope.introduceLockedSim() {
setAuthMethod(AuthenticationMethodModel.Sim)
- utils.mobileConnectionsRepository.isAnySimSecure.value = true
+ kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true
runCurrent()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index 339d026..b267720 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -22,11 +22,14 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.sceneKeys
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -39,12 +42,12 @@
@RunWith(AndroidJUnit4::class)
class SceneContainerRepositoryTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this).apply { fakeSceneContainerFlags.enabled = true }
- private val testScope = utils.testScope
+ private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+ private val testScope = kosmos.testScope
@Test
fun allSceneKeys() {
- val underTest = utils.fakeSceneContainerRepository()
+ val underTest = kosmos.sceneContainerRepository
assertThat(underTest.allSceneKeys())
.isEqualTo(
listOf(
@@ -61,7 +64,7 @@
@Test
fun desiredScene() =
testScope.runTest {
- val underTest = utils.fakeSceneContainerRepository()
+ val underTest = kosmos.sceneContainerRepository
val currentScene by collectLastValue(underTest.desiredScene)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
@@ -71,15 +74,15 @@
@Test(expected = IllegalStateException::class)
fun setDesiredScene_noSuchSceneInContainer_throws() {
- utils.kosmos.sceneKeys = listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
- val underTest = utils.fakeSceneContainerRepository()
+ kosmos.sceneKeys = listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
+ val underTest = kosmos.sceneContainerRepository
underTest.setDesiredScene(SceneModel(SceneKey.Shade))
}
@Test
fun isVisible() =
testScope.runTest {
- val underTest = utils.fakeSceneContainerRepository()
+ val underTest = kosmos.sceneContainerRepository
val isVisible by collectLastValue(underTest.isVisible)
assertThat(isVisible).isTrue()
@@ -93,19 +96,19 @@
@Test
fun transitionState_defaultsToIdle() =
testScope.runTest {
- val underTest = utils.fakeSceneContainerRepository()
+ val underTest = kosmos.sceneContainerRepository
val transitionState by collectLastValue(underTest.transitionState)
assertThat(transitionState)
.isEqualTo(
- ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey)
+ ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey)
)
}
@Test
fun transitionState_reflectsUpdates() =
testScope.runTest {
- val underTest = utils.fakeSceneContainerRepository()
+ val underTest = kosmos.sceneContainerRepository
val transitionState =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(SceneKey.Lockscreen)
@@ -134,7 +137,7 @@
underTest.setTransitionState(null)
assertThat(reflectedTransitionState)
.isEqualTo(
- ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey)
+ ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey)
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 486f7ab..d159986 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -20,10 +20,16 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.scene.data.repository.sceneContainerRepository
+import com.android.systemui.scene.sceneContainerConfig
+import com.android.systemui.scene.sceneKeys
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
@@ -36,20 +42,20 @@
@RunWith(AndroidJUnit4::class)
class SceneInteractorTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var underTest: SceneInteractor
@Before
fun setUp() {
- utils.fakeSceneContainerFlags.enabled = true
- underTest = utils.sceneInteractor()
+ kosmos.fakeSceneContainerFlags.enabled = true
+ underTest = kosmos.sceneInteractor
}
@Test
fun allSceneKeys() {
- assertThat(underTest.allSceneKeys()).isEqualTo(utils.fakeSceneKeys())
+ assertThat(underTest.allSceneKeys()).isEqualTo(kosmos.sceneKeys)
}
@Test
@@ -75,7 +81,7 @@
@Test
fun transitionState() =
testScope.runTest {
- val underTest = utils.fakeSceneContainerRepository()
+ val underTest = kosmos.sceneContainerRepository
val transitionState =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(SceneKey.Lockscreen)
@@ -104,7 +110,7 @@
underTest.setTransitionState(null)
assertThat(reflectedTransitionState)
.isEqualTo(
- ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey)
+ ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey)
)
}
@@ -348,8 +354,8 @@
@Test
fun userInput() =
testScope.runTest {
- assertThat(utils.powerRepository.userTouchRegistered).isFalse()
+ assertThat(kosmos.fakePowerRepository.userTouchRegistered).isFalse()
underTest.onUserInput()
- assertThat(utils.powerRepository.userTouchRegistered).isTrue()
+ assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
index 8be4eeb..f23716c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.scene.domain.interactor
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.statusbar.IStatusBarService
@@ -28,7 +30,11 @@
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.init.NotificationsController
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -37,6 +43,7 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -50,6 +57,7 @@
class WindowRootViewVisibilityInteractorTest : SysuiTestCase() {
private val testScope = TestScope()
+ private val testDispatcher = StandardTestDispatcher()
private val iStatusBarService = mock<IStatusBarService>()
private val executor = FakeExecutor(FakeSystemClock())
private val windowRootViewVisibilityRepository =
@@ -59,6 +67,9 @@
private val notificationPresenter = mock<NotificationPresenter>()
private val notificationsController = mock<NotificationsController>()
private val powerInteractor = PowerInteractorFactory.create().powerInteractor
+ private val activeNotificationsRepository = ActiveNotificationListRepository()
+ private val activeNotificationsInteractor =
+ ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher)
private val underTest =
WindowRootViewVisibilityInteractor(
@@ -67,6 +78,7 @@
keyguardRepository,
headsUpManager,
powerInteractor,
+ activeNotificationsInteractor,
)
.apply { setUp(notificationPresenter, notificationsController) }
@@ -257,7 +269,8 @@
}
@Test
- fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_notifCountOne() =
+ @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+ fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_flagOff_notifCountOne() =
testScope.runTest {
underTest.start()
@@ -273,6 +286,23 @@
}
@Test
+ @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+ fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_flagOn_notifCountOne() =
+ testScope.runTest {
+ underTest.start()
+
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true)
+ activeNotificationsRepository.setActiveNotifs(4)
+
+ makeLockscreenShadeVisible()
+
+ val notifCount = argumentCaptor<Int>()
+ verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+ assertThat(notifCount.value).isEqualTo(1)
+ }
+
+ @Test
fun lockscreenShadeInteractive_hasHeadsUpAndNullPresenter_notifCountOne() =
testScope.runTest {
underTest.start()
@@ -288,7 +318,8 @@
}
@Test
- fun lockscreenShadeInteractive_noHeadsUp_notifCountMatchesNotifController() =
+ @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+ fun lockscreenShadeInteractive_noHeadsUp_flagOff_notifCountMatchesNotifController() =
testScope.runTest {
underTest.start()
whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true)
@@ -304,7 +335,25 @@
}
@Test
- fun lockscreenShadeInteractive_notifPresenterNotCollapsed_notifCountMatchesNotifController() =
+ @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+ fun lockscreenShadeInteractive_noHeadsUp_flagOn_notifCountMatchesNotifController() =
+ testScope.runTest {
+ underTest.start()
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true)
+
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false)
+ activeNotificationsRepository.setActiveNotifs(9)
+
+ makeLockscreenShadeVisible()
+
+ val notifCount = argumentCaptor<Int>()
+ verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+ assertThat(notifCount.value).isEqualTo(9)
+ }
+
+ @Test
+ @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+ fun lockscreenShadeInteractive_notifPresenterNotCollapsed_flagOff_notifCountMatchesNotifController() =
testScope.runTest {
underTest.start()
whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
@@ -320,6 +369,23 @@
}
@Test
+ @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+ fun lockscreenShadeInteractive_notifPresenterNotCollapsed_flagOn_notifCountMatchesNotifController() =
+ testScope.runTest {
+ underTest.start()
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
+
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false)
+ activeNotificationsRepository.setActiveNotifs(8)
+
+ makeLockscreenShadeVisible()
+
+ val notifCount = argumentCaptor<Int>()
+ verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+ assertThat(notifCount.value).isEqualTo(8)
+ }
+
+ @Test
fun lockscreenShadeInteractive_noHeadsUp_noNotifController_notifCountZero() =
testScope.runTest {
underTest.start()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 5fe4ca1..4afa5f2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -25,19 +25,31 @@
import androidx.test.filters.SmallTest
import com.android.systemui.Flags as AconfigFlags
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.testScope
import com.android.systemui.model.SysUiState
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -65,15 +77,15 @@
@Mock private lateinit var windowController: NotificationShadeWindowController
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val sceneInteractor = utils.sceneInteractor()
- private val sceneContainerFlags = utils.fakeSceneContainerFlags
- private val authenticationInteractor = utils.authenticationInteractor()
- private val bouncerInteractor = utils.bouncerInteractor()
- private val faceAuthRepository = utils.kosmos.fakeDeviceEntryFaceAuthRepository
- private val deviceEntryInteractor = utils.deviceEntryInteractor()
- private val keyguardInteractor = utils.keyguardInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val sceneContainerFlags = kosmos.fakeSceneContainerFlags
+ private val authenticationInteractor = kosmos.authenticationInteractor
+ private val bouncerInteractor = kosmos.bouncerInteractor
+ private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
+ private val deviceEntryInteractor = kosmos.deviceEntryInteractor
+ private val keyguardInteractor = kosmos.keyguardInteractor
private val sysUiState: SysUiState = mock()
private val falsingCollector: FalsingCollector = mock()
private val powerInteractor = PowerInteractorFactory.create().powerInteractor
@@ -97,7 +109,7 @@
falsingCollector = falsingCollector,
powerInteractor = powerInteractor,
bouncerInteractor = bouncerInteractor,
- simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor },
+ simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
authenticationInteractor = dagger.Lazy { authenticationInteractor },
windowController = windowController,
)
@@ -172,7 +184,7 @@
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
underTest.start()
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@@ -188,7 +200,7 @@
assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
underTest.start()
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -204,7 +216,7 @@
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -222,7 +234,7 @@
// Authenticate using a passive auth method like face auth while bypass is disabled.
faceAuthRepository.isAuthenticated.value = true
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@@ -244,7 +256,7 @@
transitionStateFlowValue.value = ObservableTransitionState.Idle(SceneKey.Shade)
assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
runCurrent()
assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
@@ -263,7 +275,7 @@
// Authenticate using a passive auth method like face auth while bypass is disabled.
faceAuthRepository.isAuthenticated.value = true
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -373,7 +385,7 @@
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
runCurrent()
powerInteractor.setAwakeForTest()
runCurrent()
@@ -463,11 +475,11 @@
runCurrent()
verify(falsingCollector).setShowingAod(false)
- utils.keyguardRepository.setIsDozing(true)
+ kosmos.fakeKeyguardRepository.setIsDozing(true)
runCurrent()
verify(falsingCollector).setShowingAod(true)
- utils.keyguardRepository.setIsDozing(false)
+ kosmos.fakeKeyguardRepository.setIsDozing(false)
runCurrent()
verify(falsingCollector, times(2)).setShowingAod(false)
}
@@ -493,7 +505,7 @@
@Test
fun collectFalsingSignals_screenOnAndOff_aodUnavailable() =
testScope.runTest {
- utils.keyguardRepository.setAodAvailable(false)
+ kosmos.fakeKeyguardRepository.setAodAvailable(false)
runCurrent()
prepareState(
initialSceneKey = SceneKey.Lockscreen,
@@ -541,7 +553,7 @@
@Test
fun collectFalsingSignals_screenOnAndOff_aodAvailable() =
testScope.runTest {
- utils.keyguardRepository.setAodAvailable(true)
+ kosmos.fakeKeyguardRepository.setAodAvailable(true)
runCurrent()
prepareState(
initialSceneKey = SceneKey.Lockscreen,
@@ -619,7 +631,7 @@
underTest.start()
runCurrent()
- utils.mobileConnectionsRepository.isAnySimSecure.value = true
+ kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true
runCurrent()
assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
@@ -628,7 +640,7 @@
@Test
fun switchesToLockscreen_whenSimBecomesUnlocked() =
testScope.runTest {
- utils.mobileConnectionsRepository.isAnySimSecure.value = true
+ kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
@@ -638,7 +650,7 @@
)
underTest.start()
runCurrent()
- utils.mobileConnectionsRepository.isAnySimSecure.value = false
+ kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false
runCurrent()
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
@@ -647,7 +659,7 @@
@Test
fun switchesToGone_whenSimBecomesUnlocked_ifDeviceUnlockedAndLockscreenDisabled() =
testScope.runTest {
- utils.mobileConnectionsRepository.isAnySimSecure.value = true
+ kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
@@ -658,7 +670,7 @@
)
underTest.start()
runCurrent()
- utils.mobileConnectionsRepository.isAnySimSecure.value = false
+ kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false
runCurrent()
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
@@ -730,8 +742,8 @@
}
}
sceneContainerFlags.enabled = true
- utils.deviceEntryRepository.setUnlocked(isDeviceUnlocked)
- utils.deviceEntryRepository.setBypassEnabled(isBypassEnabled)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(isDeviceUnlocked)
+ kosmos.fakeDeviceEntryRepository.setBypassEnabled(isBypassEnabled)
val transitionStateFlow =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(SceneKey.Lockscreen)
@@ -743,8 +755,8 @@
sceneInteractor.onSceneChanged(SceneModel(it), "reason")
}
authenticationMethod?.let {
- utils.authenticationRepository.setAuthenticationMethod(authenticationMethod)
- utils.deviceEntryRepository.setLockscreenEnabled(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authenticationMethod)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(
isLockscreenEnabled = isLockscreenEnabled
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index 3a4ee64..ede453d8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -21,10 +21,14 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.domain.interactor.falsingInteractor
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.sceneKeys
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
@@ -36,17 +40,17 @@
@RunWith(AndroidJUnit4::class)
class SceneContainerViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val interactor = utils.sceneInteractor()
+ private val kosmos = testKosmos()
+ private val interactor = kosmos.sceneInteractor
private lateinit var underTest: SceneContainerViewModel
@Before
fun setUp() {
- utils.fakeSceneContainerFlags.enabled = true
+ kosmos.fakeSceneContainerFlags.enabled = true
underTest =
SceneContainerViewModel(
sceneInteractor = interactor,
- falsingInteractor = utils.falsingInteractor(),
+ falsingInteractor = kosmos.falsingInteractor,
)
}
@@ -64,7 +68,7 @@
@Test
fun allSceneKeys() {
- assertThat(underTest.allSceneKeys).isEqualTo(utils.fakeSceneKeys())
+ assertThat(underTest.allSceneKeys).isEqualTo(kosmos.sceneKeys)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index 77ddf15..5174502 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -7,7 +7,8 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
@@ -18,6 +19,7 @@
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow
@@ -31,9 +33,9 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class ShadeHeaderViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val sceneInteractor = utils.sceneInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index a8133a3a..bf873c2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -19,16 +19,21 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
@@ -36,6 +41,7 @@
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -53,10 +59,10 @@
@RunWith(AndroidJUnit4::class)
class ShadeSceneViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val sceneInteractor = utils.sceneInteractor()
- private val deviceEntryInteractor = utils.deviceEntryInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val deviceEntryInteractor = kosmos.deviceEntryInteractor
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
@@ -105,7 +111,7 @@
deviceEntryInteractor = deviceEntryInteractor,
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
- notifications = utils.notificationsPlaceholderViewModel(),
+ notifications = kosmos.notificationsPlaceholderViewModel,
mediaDataManager = mediaDataManager,
mediaHost = mediaHost,
)
@@ -115,8 +121,10 @@
fun upTransitionSceneKey_deviceLocked_lockScreen() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@@ -125,8 +133,10 @@
fun upTransitionSceneKey_deviceUnlocked_gone() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -135,8 +145,10 @@
fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.deviceEntryRepository.setLockscreenEnabled(true)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")
@@ -147,8 +159,10 @@
fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.deviceEntryRepository.setLockscreenEnabled(true)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason")
@@ -159,8 +173,10 @@
fun onContentClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
runCurrent()
underTest.onContentClicked()
@@ -172,8 +188,10 @@
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
runCurrent()
underTest.onContentClicked()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
index 262795f..8e8e510 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
@@ -24,12 +24,13 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.telephony.TelephonyListenerManager
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.kotlinArgumentCaptor
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -39,7 +40,6 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class TelephonyRepositoryImplTest : SysuiTestCase() {
@@ -47,8 +47,8 @@
@Mock private lateinit var manager: TelephonyListenerManager
@Mock private lateinit var telecomManager: TelecomManager
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var underTest: TelephonyRepositoryImpl
@@ -61,7 +61,7 @@
TelephonyRepositoryImpl(
applicationScope = testScope.backgroundScope,
applicationContext = context,
- backgroundDispatcher = utils.testDispatcher,
+ backgroundDispatcher = kosmos.testDispatcher,
manager = manager,
telecomManager = telecomManager,
)
diff --git a/packages/SystemUI/res/layout/biometric_prompt_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
index 23fbb12..10f7113 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
@@ -20,6 +20,13 @@
android:layout_height="wrap_content"
android:orientation="vertical">
+ <ImageView
+ android:id="@+id/logo"
+ android:layout_width="@dimen/biometric_auth_icon_size"
+ android:layout_height="@dimen/biometric_auth_icon_size"
+ android:layout_gravity="center"
+ android:scaleType="fitXY"/>
+
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 85b6e8d..5db9eee 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -50,7 +50,10 @@
app:layout_constraintStart_toStartOf="@id/album_art"
app:layout_constraintEnd_toEndOf="@id/album_art"
app:layout_constraintTop_toTopOf="@id/album_art"
- app:layout_constraintBottom_toBottomOf="@id/album_art" />
+ app:layout_constraintBottom_toBottomOf="@id/album_art"
+ android:clipToOutline="true"
+ android:background="@drawable/qs_media_outline_layout_bg"
+ />
<com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
android:id="@+id/turbulence_noise_view"
@@ -59,7 +62,10 @@
app:layout_constraintStart_toStartOf="@id/album_art"
app:layout_constraintEnd_toEndOf="@id/album_art"
app:layout_constraintTop_toTopOf="@id/album_art"
- app:layout_constraintBottom_toBottomOf="@id/album_art" />
+ app:layout_constraintBottom_toBottomOf="@id/album_art"
+ android:clipToOutline="true"
+ android:background="@drawable/qs_media_outline_layout_bg"
+ />
<!-- Guideline for output switcher -->
<androidx.constraintlayout.widget.Guideline
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 798fc06b..ee2a1ce 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -897,6 +897,10 @@
<dimen name="communal_tutorial_indicator_padding">24dp</dimen>
<dimen name="communal_tutorial_indicator_horizontal_offset">32dp</dimen>
+ <!-- Size of the maximum radius for the enforced rounded rectangles on communal hub.
+ Keep it the same as in Launcher-->
+ <dimen name="communal_enforced_rounded_corner_max_radius">16dp</dimen>
+
<!-- The width/height of the unlock icon view on keyguard. -->
<dimen name="keyguard_lock_height">42dp</dimen>
<dimen name="keyguard_lock_padding">20dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 6e035e8..ec4c7d5 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -249,6 +249,9 @@
-->
<item type="id" name="tag_smartspace_view" />
+ <!-- ID of the Scene Container root Composable view -->
+ <item type='id' name="scene_container_root_composable" />
+
<!-- Tag set on the Compose implementation of the QS footer actions. -->
<item type="id" name="tag_compose_qs_footer_actions" />
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
index 6483ae4..c7e5b64 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
@@ -19,16 +19,20 @@
import android.os.UserHandle
import android.provider.Settings.Secure
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.withContext
/** Provides data related to color correction. */
@@ -45,22 +49,24 @@
@Inject
constructor(
@Background private val bgCoroutineContext: CoroutineContext,
+ @Application private val scope: CoroutineScope,
private val secureSettings: SecureSettings,
) : ColorCorrectionRepository {
- companion object {
- const val SETTING_NAME = Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED
- const val DISABLED = 0
- const val ENABLED = 1
- }
+ private val userMap = mutableMapOf<Int, Flow<Boolean>>()
override fun isEnabled(userHandle: UserHandle): Flow<Boolean> =
- secureSettings
- .observerFlow(userHandle.identifier, SETTING_NAME)
- .onStart { emit(Unit) }
- .map { secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED }
- .distinctUntilChanged()
- .flowOn(bgCoroutineContext)
+ userMap.getOrPut(userHandle.identifier) {
+ secureSettings
+ .observerFlow(userHandle.identifier, SETTING_NAME)
+ .onStart { emit(Unit) }
+ .map {
+ secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED
+ }
+ .distinctUntilChanged()
+ .flowOn(bgCoroutineContext)
+ .shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1)
+ }
override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean =
withContext(bgCoroutineContext) {
@@ -70,4 +76,10 @@
userHandle.identifier
)
}
+
+ companion object {
+ private const val SETTING_NAME = Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED
+ private const val DISABLED = 0
+ private const val ENABLED = 1
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
index bbf10c5..419eada 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
@@ -19,16 +19,20 @@
import android.os.UserHandle
import android.provider.Settings.Secure
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.withContext
/** Provides data related to color inversion. */
@@ -45,16 +49,24 @@
@Inject
constructor(
@Background private val bgCoroutineContext: CoroutineContext,
+ @Application private val scope: CoroutineScope,
private val secureSettings: SecureSettings,
) : ColorInversionRepository {
+ private val userMap = mutableMapOf<Int, Flow<Boolean>>()
+
override fun isEnabled(userHandle: UserHandle): Flow<Boolean> =
- secureSettings
- .observerFlow(userHandle.identifier, SETTING_NAME)
- .onStart { emit(Unit) }
- .map { secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED }
- .distinctUntilChanged()
- .flowOn(bgCoroutineContext)
+ userMap.getOrPut(userHandle.identifier) {
+ secureSettings
+ .observerFlow(userHandle.identifier, SETTING_NAME)
+ .onStart { emit(Unit) }
+ .map {
+ secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED
+ }
+ .distinctUntilChanged()
+ .flowOn(bgCoroutineContext)
+ .shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1)
+ }
override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean =
withContext(bgCoroutineContext) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index ab23564..57e308f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -399,7 +399,8 @@
config.mPromptInfo,
config.mUserId,
config.mOperationId,
- new BiometricModalities(fpProps, faceProps));
+ new BiometricModalities(fpProps, faceProps),
+ config.mOpPackageName);
final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate(
R.layout.biometric_prompt_layout, null, false);
@@ -470,7 +471,8 @@
mBackgroundView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
mPromptSelectorInteractorProvider.get().useCredentialsForAuthentication(
- mConfig.mPromptInfo, credentialType, mConfig.mUserId, mConfig.mOperationId);
+ mConfig.mPromptInfo, credentialType, mConfig.mUserId, mConfig.mOperationId,
+ mConfig.mOpPackageName);
final CredentialViewModel vm = mCredentialViewModelProvider.get();
vm.setAnimateContents(animateContents);
((CredentialView) mCredentialView).init(vm, this, mPanelController, animatePanel);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
index 86802a5b..02eae9ced 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
@@ -148,6 +148,12 @@
|| child.getId() == R.id.customized_view_container) {
//skip description view and compute later
continue;
+ } else if (child.getId() == R.id.logo) {
+ child.measure(
+ MeasureSpec.makeMeasureSpec(child.getLayoutParams().width,
+ MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
+ MeasureSpec.EXACTLY));
} else {
child.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
index b35fbbc..ad7bb0e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
@@ -55,6 +55,9 @@
/** The kind of credential to use (biometric, pin, pattern, etc.). */
val kind: StateFlow<PromptKind>
+ /** The package name that the prompt is called from. */
+ val opPackageName: StateFlow<String?>
+
/**
* If explicit confirmation is required.
*
@@ -68,6 +71,7 @@
userId: Int,
gatekeeperChallenge: Long?,
kind: PromptKind,
+ opPackageName: String,
)
/** Unset the prompt info. */
@@ -108,6 +112,9 @@
private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.Biometric())
override val kind = _kind.asStateFlow()
+ private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null)
+ override val opPackageName = _opPackageName.asStateFlow()
+
private val _faceSettings =
_userId.map { id -> faceSettings.forUser(id) }.distinctUntilChanged()
private val _faceSettingAlwaysRequireConfirmation =
@@ -127,11 +134,13 @@
userId: Int,
gatekeeperChallenge: Long?,
kind: PromptKind,
+ opPackageName: String,
) {
_kind.value = kind
_userId.value = userId
_challenge.value = gatekeeperChallenge
_promptInfo.value = promptInfo
+ _opPackageName.value = opPackageName
}
override fun unsetPrompt() {
@@ -139,6 +148,7 @@
_userId.value = null
_challenge.value = null
_kind.value = PromptKind.Biometric()
+ _opPackageName.value = null
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
index ac4b717..359e2e7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
@@ -115,12 +115,14 @@
@Utils.CredentialType kind: Int,
userId: Int,
challenge: Long,
+ opPackageName: String,
) {
biometricPromptRepository.setPrompt(
promptInfo,
userId,
challenge,
- kind.asBiometricPromptCredential()
+ kind.asBiometricPromptCredential(),
+ opPackageName,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
index 65a2c0a..b3f9574 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
@@ -76,6 +76,7 @@
userId: Int,
challenge: Long,
modalities: BiometricModalities,
+ opPackageName: String,
)
/** Use credential-based authentication instead of biometrics. */
@@ -84,6 +85,7 @@
@Utils.CredentialType kind: Int,
userId: Int,
challenge: Long,
+ opPackageName: String,
)
/** Unset the current authentication request. */
@@ -104,9 +106,12 @@
promptRepository.promptInfo,
promptRepository.challenge,
promptRepository.userId,
- promptRepository.kind
- ) { promptInfo, challenge, userId, kind ->
- if (promptInfo == null || userId == null || challenge == null) {
+ promptRepository.kind,
+ promptRepository.opPackageName,
+ ) { promptInfo, challenge, userId, kind, opPackageName ->
+ if (
+ promptInfo == null || userId == null || challenge == null || opPackageName == null
+ ) {
return@combine null
}
@@ -117,6 +122,7 @@
userInfo = BiometricUserInfo(userId = userId),
operationInfo = BiometricOperationInfo(gatekeeperChallenge = challenge),
modalities = kind.activeModalities,
+ opPackageName = opPackageName,
)
else -> null
}
@@ -152,13 +158,15 @@
promptInfo: PromptInfo,
userId: Int,
challenge: Long,
- modalities: BiometricModalities
+ modalities: BiometricModalities,
+ opPackageName: String,
) {
promptRepository.setPrompt(
promptInfo = promptInfo,
userId = userId,
gatekeeperChallenge = challenge,
kind = PromptKind.Biometric(modalities),
+ opPackageName = opPackageName,
)
}
@@ -167,12 +175,14 @@
@Utils.CredentialType kind: Int,
userId: Int,
challenge: Long,
+ opPackageName: String,
) {
promptRepository.setPrompt(
promptInfo = promptInfo,
userId = userId,
gatekeeperChallenge = challenge,
kind = kind.asBiometricPromptCredential(),
+ opPackageName = opPackageName,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
index 4377937..c17c8dc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.biometrics.domain.model
+import android.graphics.Bitmap
import android.hardware.biometrics.PromptContentView
import android.hardware.biometrics.PromptInfo
import com.android.systemui.biometrics.shared.model.BiometricModalities
@@ -26,6 +27,7 @@
userInfo: BiometricUserInfo,
operationInfo: BiometricOperationInfo,
val modalities: BiometricModalities,
+ val opPackageName: String,
) :
BiometricPromptRequest(
title = info.title?.toString() ?: "",
@@ -36,6 +38,8 @@
showEmergencyCallButton = info.isShowEmergencyCallButton
) {
val contentView: PromptContentView? = info.contentView
+ val logoRes: Int = info.logoRes
+ val logoBitmap: Bitmap? = info.logoBitmap
val negativeButtonText: String = info.negativeButtonText?.toString() ?: ""
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
index 60b454e..b450896 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
@@ -115,6 +115,12 @@
MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(iconView.getLayoutParams().height,
MeasureSpec.EXACTLY));
+ } else if (child.getId() == R.id.logo) {
+ child.measure(
+ MeasureSpec.makeMeasureSpec(child.getLayoutParams().width,
+ MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
+ MeasureSpec.EXACTLY));
} else if (child.getId() == R.id.biometric_icon) {
child.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 04dc7a8..285ab4a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -31,6 +31,7 @@
import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO
import android.view.accessibility.AccessibilityManager
import android.widget.Button
+import android.widget.ImageView
import android.widget.ScrollView
import android.widget.TextView
import androidx.lifecycle.DefaultLifecycleObserver
@@ -92,6 +93,7 @@
val textColorHint =
view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme)
+ val logoView = view.requireViewById<ImageView>(R.id.logo)
val titleView = view.requireViewById<TextView>(R.id.title)
val subtitleView = view.requireViewById<TextView>(R.id.subtitle)
val descriptionView = view.requireViewById<TextView>(R.id.description)
@@ -99,6 +101,8 @@
view.requireViewById<ScrollView>(R.id.customized_view_container)
// set selected to enable marquee unless a screen reader is enabled
+ logoView.isSelected =
+ !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
titleView.isSelected =
!accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
subtitleView.isSelected =
@@ -152,6 +156,7 @@
}
}
+ logoView.setImageDrawable(viewModel.logo.first())
titleView.text = viewModel.title.first()
subtitleView.text = viewModel.subtitle.first()
descriptionView.text = viewModel.description.first()
@@ -183,6 +188,7 @@
viewModel = viewModel,
viewsToHideWhenSmall =
listOf(
+ logoView,
titleView,
subtitleView,
descriptionView,
@@ -190,6 +196,7 @@
),
viewsToFadeInOnSizeChange =
listOf(
+ logoView,
titleView,
subtitleView,
descriptionView,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index c3bbaed..d5695f3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -25,6 +25,7 @@
import android.view.WindowInsets
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
+import android.widget.ImageView
import android.widget.TextView
import androidx.core.animation.addListener
import androidx.core.view.doOnLayout
@@ -234,7 +235,13 @@
private fun View.showContentOrHide(forceHide: Boolean = false) {
val isTextViewWithBlankText = this is TextView && this.text.isBlank()
- visibility = if (forceHide || isTextViewWithBlankText) View.GONE else View.VISIBLE
+ val isImageViewWithoutImage = this is ImageView && this.drawable == null
+ visibility =
+ if (forceHide || isTextViewWithBlankText || isImageViewWithoutImage) {
+ View.GONE
+ } else {
+ View.VISIBLE
+ }
}
private fun View.asVerticalAnimator(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 1c78928..dca0338 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -18,6 +18,8 @@
import android.content.Context
import android.graphics.Rect
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
import android.hardware.biometrics.BiometricPrompt
import android.hardware.biometrics.PromptContentView
import android.util.Log
@@ -233,6 +235,19 @@
}
}
+ /** Logo for the prompt. */
+ val logo: Flow<Drawable?> =
+ promptSelectorInteractor.prompt
+ .map {
+ when {
+ it == null -> null
+ it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme)
+ it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
+ else -> context.packageManager.getApplicationIcon(it.opPackageName)
+ }
+ }
+ .distinctUntilChanged()
+
/** Title for the prompt. */
val title: Flow<String> =
promptSelectorInteractor.prompt.map { it?.title ?: "" }.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index 10768ea..dc07c1b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -18,6 +18,7 @@
import com.android.systemui.communal.data.db.CommunalDatabaseModule
import com.android.systemui.communal.data.repository.CommunalMediaRepositoryModule
+import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryModule
import com.android.systemui.communal.data.repository.CommunalRepositoryModule
import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
@@ -34,6 +35,7 @@
CommunalTutorialRepositoryModule::class,
CommunalWidgetRepositoryModule::class,
CommunalDatabaseModule::class,
+ CommunalPrefsRepositoryModule::class,
]
)
interface CommunalModule {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
new file mode 100644
index 0000000..c2ea2e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.repository
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.content.pm.UserInfo
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserFileManagerExt.observeSharedPreferences
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/**
+ * Stores simple preferences for the current user in communal hub. For use cases like "has the CTA
+ * tile been dismissed?"
+ */
+interface CommunalPrefsRepository {
+
+ /** Whether the CTA tile has been dismissed. */
+ val isCtaDismissed: Flow<Boolean>
+
+ /** Save the CTA tile dismissed state for the current user. */
+ suspend fun setCtaDismissedForCurrentUser()
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class CommunalPrefsRepositoryImpl
+@Inject
+constructor(
+ @Background private val backgroundScope: CoroutineScope,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ private val userRepository: UserRepository,
+ private val userFileManager: UserFileManager,
+) : CommunalPrefsRepository {
+
+ override val isCtaDismissed: Flow<Boolean> =
+ userRepository.selectedUserInfo
+ .flatMapLatest(::observeCtaDismissState)
+ .stateIn(
+ scope = backgroundScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
+ override suspend fun setCtaDismissedForCurrentUser() =
+ withContext(bgDispatcher) {
+ getSharedPrefsForUser(userRepository.getSelectedUserInfo())
+ .edit()
+ .putBoolean(CTA_DISMISSED_STATE, true)
+ .apply()
+ }
+
+ private fun observeCtaDismissState(user: UserInfo): Flow<Boolean> =
+ userFileManager
+ .observeSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, user.id)
+ // Emit at the start of collection to ensure we get an initial value
+ .onStart { emit(Unit) }
+ .map { getCtaDismissedState() }
+ .flowOn(bgDispatcher)
+
+ private suspend fun getCtaDismissedState(): Boolean =
+ withContext(bgDispatcher) {
+ getSharedPrefsForUser(userRepository.getSelectedUserInfo())
+ .getBoolean(CTA_DISMISSED_STATE, false)
+ }
+
+ private fun getSharedPrefsForUser(user: UserInfo): SharedPreferences {
+ return userFileManager.getSharedPreferences(
+ FILE_NAME,
+ Context.MODE_PRIVATE,
+ user.id,
+ )
+ }
+
+ companion object {
+ const val TAG = "CommunalRepository"
+ const val FILE_NAME = "communal_hub_prefs"
+ const val CTA_DISMISSED_STATE = "cta_dismissed"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryModule.kt
new file mode 100644
index 0000000..a4ff6d3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryModule.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.communal.data.repository
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface CommunalPrefsRepositoryModule {
+ @Binds fun communalPrefsRepository(impl: CommunalPrefsRepositoryImpl): CommunalPrefsRepository
+}
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 553b3eb..1f4be40 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
@@ -56,9 +56,6 @@
/** Exposes the transition state of the communal [SceneTransitionLayout]. */
val transitionState: StateFlow<ObservableCommunalTransitionState>
- /** Whether the CTA tile is visible in the hub under view mode. */
- val isCtaTileInViewModeVisible: Flow<Boolean>
-
/** Updates the requested scene. */
fun setDesiredScene(desiredScene: CommunalSceneKey)
@@ -68,9 +65,6 @@
* Note that you must call is with `null` when the UI is done or risk a memory leak.
*/
fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?)
-
- /** Updates whether to display the CTA tile in the hub under view mode. */
- fun setCtaTileInViewModeVisibility(isVisible: Boolean)
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -102,16 +96,6 @@
initialValue = defaultTransitionState,
)
- // TODO(b/313462210) - persist the value in local storage, so the tile won't show up again
- // once dismissed.
- private val _isCtaTileInViewModeVisible: MutableStateFlow<Boolean> = MutableStateFlow(true)
- override val isCtaTileInViewModeVisible: Flow<Boolean> =
- _isCtaTileInViewModeVisible.asStateFlow()
-
- override fun setCtaTileInViewModeVisibility(isVisible: Boolean) {
- _isCtaTileInViewModeVisible.value = isVisible
- }
-
override fun setDesiredScene(desiredScene: CommunalSceneKey) {
_desiredScene.value = desiredScene
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index e6816e9..bfc5019 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -16,7 +16,6 @@
package com.android.systemui.communal.data.repository
-import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Intent
@@ -29,6 +28,7 @@
import com.android.systemui.communal.data.db.CommunalWidgetItem
import com.android.systemui.communal.shared.CommunalWidgetHost
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -83,7 +83,7 @@
@Inject
constructor(
private val appWidgetManager: Optional<AppWidgetManager>,
- private val appWidgetHost: AppWidgetHost,
+ private val appWidgetHost: CommunalAppWidgetHost,
@Application private val applicationScope: CoroutineScope,
@Background private val bgDispatcher: CoroutineDispatcher,
broadcastDispatcher: BroadcastDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
index d0d9e3f..52f42c1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
@@ -17,11 +17,11 @@
package com.android.systemui.communal.data.repository
-import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.res.Resources
import com.android.systemui.communal.shared.CommunalWidgetHost
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
@@ -48,15 +48,15 @@
@SysUISingleton
@Provides
- fun provideAppWidgetHost(@Application context: Context): AppWidgetHost {
- return AppWidgetHost(context, APP_WIDGET_HOST_ID)
+ fun provideCommunalAppWidgetHost(@Application context: Context): CommunalAppWidgetHost {
+ return CommunalAppWidgetHost(context, APP_WIDGET_HOST_ID)
}
@SysUISingleton
@Provides
fun provideCommunalWidgetHost(
appWidgetManager: Optional<AppWidgetManager>,
- appWidgetHost: AppWidgetHost,
+ appWidgetHost: CommunalAppWidgetHost,
@CommunalLog logBuffer: LogBuffer,
): CommunalWidgetHost {
return CommunalWidgetHost(appWidgetManager, appWidgetHost, logBuffer)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 9fa4cd6..aa4a9d0 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -17,9 +17,9 @@
package com.android.systemui.communal.domain.interactor
import android.app.smartspace.SmartspaceTarget
-import android.appwidget.AppWidgetHost
import android.content.ComponentName
import com.android.systemui.communal.data.repository.CommunalMediaRepository
+import com.android.systemui.communal.data.repository.CommunalPrefsRepository
import com.android.systemui.communal.data.repository.CommunalRepository
import com.android.systemui.communal.data.repository.CommunalWidgetRepository
import com.android.systemui.communal.domain.model.CommunalContentModel
@@ -29,6 +29,7 @@
import com.android.systemui.communal.shared.model.CommunalContentSize.THIRD
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -49,10 +50,11 @@
constructor(
private val communalRepository: CommunalRepository,
private val widgetRepository: CommunalWidgetRepository,
+ private val communalPrefsRepository: CommunalPrefsRepository,
mediaRepository: CommunalMediaRepository,
smartspaceRepository: SmartspaceRepository,
keyguardInteractor: KeyguardInteractor,
- private val appWidgetHost: AppWidgetHost,
+ private val appWidgetHost: CommunalAppWidgetHost,
private val editWidgetsActivityStarter: EditWidgetsActivityStarter
) {
@@ -122,7 +124,7 @@
}
/** Dismiss the CTA tile from the hub in view mode. */
- fun dismissCtaTile() = communalRepository.setCtaTileInViewModeVisibility(isVisible = false)
+ suspend fun dismissCtaTile() = communalPrefsRepository.setCtaDismissedForCurrentUser()
/**
* Add a widget at the specified position.
@@ -174,8 +176,8 @@
/** CTA tile to be displayed in the glanceable hub (view mode). */
val ctaTileContent: Flow<List<CommunalContentModel.CtaTileInViewMode>> =
- communalRepository.isCtaTileInViewModeVisible.map { visible ->
- if (visible) listOf(CommunalContentModel.CtaTileInViewMode()) else emptyList()
+ communalPrefsRepository.isCtaDismissed.map { isDismissed ->
+ if (isDismissed) emptyList() else listOf(CommunalContentModel.CtaTileInViewMode())
}
/** A list of tutorial content to be displayed in the communal hub in tutorial mode. */
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index 46f957f..0d52afd 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -16,10 +16,10 @@
package com.android.systemui.communal.domain.model
-import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetProviderInfo
import android.widget.RemoteViews
import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import java.util.UUID
/** Encapsulates data for a communal content. */
@@ -44,7 +44,7 @@
class Widget(
val appWidgetId: Int,
val providerInfo: AppWidgetProviderInfo,
- val appWidgetHost: AppWidgetHost,
+ val appWidgetHost: CommunalAppWidgetHost,
) : CommunalContentModel {
override val key = KEY.widget(appWidgetId)
// Widget size is always half.
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
index 41f9cb4..7fe37cc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
@@ -16,11 +16,11 @@
package com.android.systemui.communal.shared
-import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL
import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE
import android.content.ComponentName
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
@@ -35,7 +35,7 @@
@Inject
constructor(
private val appWidgetManager: Optional<AppWidgetManager>,
- private val appWidgetHost: AppWidgetHost,
+ private val appWidgetHost: CommunalAppWidgetHost,
@CommunalLog logBuffer: LogBuffer,
) {
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index fcad45f..317dd40 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -20,7 +20,6 @@
import android.app.Activity.RESULT_CANCELED
import android.app.Activity.RESULT_OK
import android.app.ActivityOptions
-import android.appwidget.AppWidgetHost
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.widget.RemoteViews
@@ -28,6 +27,7 @@
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.log.CommunalUiEvent
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.media.dagger.MediaModule
@@ -48,7 +48,7 @@
@Inject
constructor(
private val communalInteractor: CommunalInteractor,
- private val appWidgetHost: AppWidgetHost,
+ private val appWidgetHost: CommunalAppWidgetHost,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
private val uiEventLogger: UiEventLogger,
) : BaseCommunalViewModel(communalInteractor, mediaHost) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 09c18ed..d619362 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -86,9 +86,11 @@
override fun onOpenWidgetEditor() = communalInteractor.showWidgetEditor()
override fun onDismissCtaTile() {
- communalInteractor.dismissCtaTile()
- setPopupOnDismissCtaVisibility(true)
- schedulePopupHiding()
+ scope.launch {
+ communalInteractor.dismissCtaTile()
+ setPopupOnDismissCtaVisibility(true)
+ schedulePopupHiding()
+ }
}
override fun getInteractionHandler(): RemoteViews.InteractionHandler = interactionHandler
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
new file mode 100644
index 0000000..003c9d5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import android.appwidget.AppWidgetHost
+import android.appwidget.AppWidgetHostView
+import android.appwidget.AppWidgetProviderInfo
+import android.content.Context
+
+/** Communal app widget host that creates a [CommunalAppWidgetHostView]. */
+class CommunalAppWidgetHost(context: Context, hostId: Int) : AppWidgetHost(context, hostId) {
+ override fun onCreateView(
+ context: Context,
+ appWidgetId: Int,
+ appWidget: AppWidgetProviderInfo?
+ ): AppWidgetHostView {
+ return CommunalAppWidgetHostView(context)
+ }
+
+ /**
+ * Creates and returns a [CommunalAppWidgetHostView]. This method does the same thing as
+ * `createView`. The only difference is that the returned value will be casted to
+ * [CommunalAppWidgetHostView].
+ */
+ fun createViewForCommunal(
+ context: Context?,
+ appWidgetId: Int,
+ appWidget: AppWidgetProviderInfo?
+ ): CommunalAppWidgetHostView {
+ // `createView` internally calls `onCreateView` to create the view. We cannot override
+ // `createView`, but we are sure that the hostView is `CommunalAppWidgetHostView`
+ return createView(context, appWidgetId, appWidget) as CommunalAppWidgetHostView
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
new file mode 100644
index 0000000..2b7d823
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import android.appwidget.AppWidgetHostView
+import android.content.Context
+import android.graphics.Outline
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewOutlineProvider
+
+/** AppWidgetHostView that displays in communal hub with support for rounded corners. */
+class CommunalAppWidgetHostView(context: Context) : AppWidgetHostView(context) {
+ // Mutable corner radius.
+ var enforcedCornerRadius: Float
+
+ // Mutable `Rect`. The size will be mutated when the widget is reapplied.
+ var enforcedRectangle: Rect
+
+ init {
+ enforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context)
+ enforcedRectangle = Rect()
+ }
+
+ override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+ super.onLayout(changed, l, t, r, b)
+
+ enforceRoundedCorners()
+ }
+
+ private val cornerRadiusEnforcementOutline: ViewOutlineProvider =
+ object : ViewOutlineProvider() {
+ override fun getOutline(view: View?, outline: Outline) {
+ if (enforcedRectangle.isEmpty || enforcedCornerRadius <= 0) {
+ outline.setEmpty()
+ } else {
+ outline.setRoundRect(enforcedRectangle, enforcedCornerRadius)
+ }
+ }
+ }
+
+ private fun enforceRoundedCorners() {
+ if (enforcedCornerRadius <= 0) {
+ resetRoundedCorners()
+ return
+ }
+ val background: View? = RoundedCornerEnforcement.findBackground(this)
+ if (background == null || RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) {
+ resetRoundedCorners()
+ return
+ }
+ RoundedCornerEnforcement.computeRoundedRectangle(this, background, enforcedRectangle)
+ outlineProvider = cornerRadiusEnforcementOutline
+ clipToOutline = true
+ invalidateOutline()
+ }
+
+ private fun resetRoundedCorners() {
+ outlineProvider = ViewOutlineProvider.BACKGROUND
+ clipToOutline = false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt
new file mode 100644
index 0000000..abda44b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import android.annotation.IdRes
+import android.annotation.Nullable
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.os.BuildCompat.isAtLeastS
+import com.android.systemui.res.R
+import kotlin.math.min
+
+/**
+ * Utilities to compute the enforced use of rounded corners on App Widgets. This is a fork of the
+ * Launcher3 source code to enforce the same visual treatment on communal hub.
+ */
+internal object RoundedCornerEnforcement {
+ /**
+ * Find the background view for a widget.
+ *
+ * @param appWidget the view containing the App Widget (typically the instance of
+ * [CommunalAppWidgetHostView]).
+ */
+ fun findBackground(appWidget: View): View? {
+ val backgrounds = findViewsWithId(appWidget, R.id.background)
+ if (backgrounds.size == 1) {
+ return backgrounds[0]
+ }
+ // Really, the argument should contain the widget, so it cannot be the background.
+ if (appWidget is ViewGroup) {
+ val vg = appWidget
+ if (vg.childCount > 0) {
+ return findUndefinedBackground(vg.getChildAt(0))
+ }
+ }
+ return appWidget
+ }
+
+ /** Check whether the app widget has opted out of the enforcement. */
+ fun hasAppWidgetOptedOut(appWidget: View?, background: View): Boolean {
+ return background.id == R.id.background && background.clipToOutline
+ }
+
+ /**
+ * Computes the rounded rectangle needed for this app widget.
+ *
+ * @param appWidget View onto which the rounded rectangle will be applied.
+ * @param background Background view. This must be either `appWidget` or a descendant of
+ * `appWidget`.
+ * @param outRect Rectangle set to the rounded rectangle coordinates, in the reference frame of
+ * `appWidget`.
+ */
+ fun computeRoundedRectangle(appWidget: View, background: View, outRect: Rect) {
+ var background = background
+ outRect.left = 0
+ outRect.right = background.width
+ outRect.top = 0
+ outRect.bottom = background.height
+ while (background !== appWidget) {
+ outRect.offset(background.left, background.top)
+ background = background.parent as View
+ }
+ }
+
+ /** Get the radius of the rounded rectangle defined in the host's resource. */
+ private fun getOwnedEnforcedRadius(context: Context): Float {
+ val res: Resources = context.resources
+ return res.getDimension(R.dimen.communal_enforced_rounded_corner_max_radius)
+ }
+
+ /**
+ * Computes the radius of the rounded rectangle that should be applied to a widget expanded in
+ * the given context.
+ */
+ fun computeEnforcedRadius(context: Context): Float {
+ if (!isAtLeastS()) {
+ return 0f
+ }
+ val res: Resources = context.resources
+ val systemRadius: Float =
+ res.getDimension(android.R.dimen.system_app_widget_background_radius)
+ val defaultRadius = getOwnedEnforcedRadius(context)
+ return min(defaultRadius, systemRadius)
+ }
+
+ private fun findViewsWithId(view: View, @IdRes viewId: Int): List<View> {
+ val output: MutableList<View> = ArrayList()
+ accumulateViewsWithId(view, viewId, output)
+ return output
+ }
+
+ // Traverse views. If the predicate returns true, continue on the children, otherwise, don't.
+ private fun accumulateViewsWithId(view: View, @IdRes viewId: Int, output: MutableList<View>) {
+ if (view.id == viewId) {
+ output.add(view)
+ return
+ }
+ if (view is ViewGroup) {
+ val vg = view
+ for (i in 0 until vg.childCount) {
+ accumulateViewsWithId(vg.getChildAt(i), viewId, output)
+ }
+ }
+ }
+
+ private fun isViewVisible(view: View): Boolean {
+ return if (view.visibility != View.VISIBLE) {
+ false
+ } else !view.willNotDraw() || view.foreground != null || view.background != null
+ }
+
+ @Nullable
+ private fun findUndefinedBackground(current: View): View? {
+ if (current.visibility != View.VISIBLE) {
+ return null
+ }
+ if (isViewVisible(current)) {
+ return current
+ }
+ var lastVisibleView: View? = null
+ // Find the first view that is either not a ViewGroup, or a ViewGroup which will draw
+ // something, or a ViewGroup that contains more than one view.
+ if (current is ViewGroup) {
+ val vg = current
+ for (i in 0 until vg.childCount) {
+ val visibleView = findUndefinedBackground(vg.getChildAt(i))
+ if (visibleView != null) {
+ if (lastVisibleView != null) {
+ return current // At least two visible children
+ }
+ lastVisibleView = visibleView
+ }
+ }
+ }
+ return lastVisibleView
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt
index 496c64e..c6fb4f9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt
@@ -19,6 +19,8 @@
import com.android.systemui.keyboard.data.repository.KeyboardRepository
import com.android.systemui.keyboard.data.repository.KeyboardRepositoryImpl
+import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepository
+import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepositoryImpl
import dagger.Binds
import dagger.Module
@@ -27,4 +29,9 @@
@Binds
abstract fun bindKeyboardRepository(repository: KeyboardRepositoryImpl): KeyboardRepository
+
+ @Binds
+ abstract fun bindStickyKeysRepository(
+ repository: StickyKeysRepositoryImpl
+ ): StickyKeysRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt
new file mode 100644
index 0000000..37034f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard.stickykeys
+
+import com.android.systemui.keyboard.stickykeys.shared.model.Locked
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.KeyboardLog
+import javax.inject.Inject
+
+private const val TAG = "stickyKeys"
+
+class StickyKeysLogger @Inject constructor(@KeyboardLog private val buffer: LogBuffer) {
+ fun logNewStickyKeysReceived(linkedHashMap: Map<ModifierKey, Locked>) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ { str1 = linkedHashMap.toString() },
+ { "new sticky keys state received: $str1" }
+ )
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
new file mode 100644
index 0000000..34d2888
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard.stickykeys.data.repository
+
+import android.hardware.input.InputManager
+import android.hardware.input.InputManager.StickyModifierStateListener
+import android.hardware.input.StickyModifierState
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
+import com.android.systemui.keyboard.stickykeys.shared.model.Locked
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import javax.inject.Inject
+
+interface StickyKeysRepository {
+ val stickyKeys: Flow<LinkedHashMap<ModifierKey, Locked>>
+ val settingEnabled: Flow<Boolean>
+}
+
+class StickyKeysRepositoryImpl
+@Inject
+constructor(
+ private val inputManager: InputManager,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val stickyKeysLogger: StickyKeysLogger,
+) : StickyKeysRepository {
+
+ override val stickyKeys: Flow<LinkedHashMap<ModifierKey, Locked>> =
+ conflatedCallbackFlow {
+ val listener = StickyModifierStateListener { stickyModifierState ->
+ trySendWithFailureLogging(stickyModifierState, TAG)
+ }
+ // after registering, InputManager calls listener with the current value
+ inputManager.registerStickyModifierStateListener(Runnable::run, listener)
+ awaitClose { inputManager.unregisterStickyModifierStateListener(listener) }
+ }
+ .map { toStickyKeysMap(it) }
+ .onEach { stickyKeysLogger.logNewStickyKeysReceived(it) }
+ .flowOn(backgroundDispatcher)
+
+ // TODO(b/319837892): Implement reading actual setting
+ override val settingEnabled: StateFlow<Boolean> = MutableStateFlow(true)
+
+ private fun toStickyKeysMap(state: StickyModifierState): LinkedHashMap<ModifierKey, Locked> {
+ val keys = linkedMapOf<ModifierKey, Locked>()
+ state.apply {
+ if (isAltGrModifierOn) keys[ALT_GR] = Locked(false)
+ if (isAltGrModifierLocked) keys[ALT_GR] = Locked(true)
+ if (isAltModifierOn) keys[ALT] = Locked(false)
+ if (isAltModifierLocked) keys[ALT] = Locked(true)
+ if (isCtrlModifierOn) keys[CTRL] = Locked(false)
+ if (isCtrlModifierLocked) keys[CTRL] = Locked(true)
+ if (isMetaModifierOn) keys[META] = Locked(false)
+ if (isMetaModifierLocked) keys[META] = Locked(true)
+ if (isShiftModifierOn) keys[SHIFT] = Locked(false)
+ if (isShiftModifierLocked) keys[SHIFT] = Locked(true)
+ }
+ return keys
+ }
+
+ companion object {
+ const val TAG = "StickyKeysRepositoryImpl"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
new file mode 100644
index 0000000..d5f082a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard.stickykeys.shared.model
+
+@JvmInline
+value class Locked(val locked: Boolean)
+
+enum class ModifierKey(val text: String) {
+ ALT("ALT LEFT"),
+ ALT_GR("ALT RIGHT"),
+ CTRL("CTRL"),
+ META("META"),
+ SHIFT("SHIFT"),
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModel.kt
new file mode 100644
index 0000000..26eb706
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModel.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard.stickykeys.ui.viewmodel
+
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyboard.data.repository.KeyboardRepository
+import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepository
+import com.android.systemui.keyboard.stickykeys.shared.model.Locked
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.stateIn
+import javax.inject.Inject
+
+class StickyKeysIndicatorViewModel
+@Inject
+constructor(
+ stickyKeysRepository: StickyKeysRepository,
+ keyboardRepository: KeyboardRepository,
+ @Application applicationScope: CoroutineScope,
+) {
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ val indicatorContent: Flow<Map<ModifierKey, Locked>> =
+ keyboardRepository.isAnyKeyboardConnected
+ .flatMapLatest { keyboardPresent ->
+ if (keyboardPresent) stickyKeysRepository.settingEnabled else flowOf(false)
+ }
+ .flatMapLatest { enabled ->
+ if (enabled) stickyKeysRepository.stickyKeys else flowOf(emptyMap())
+ }
+ .stateIn(applicationScope, SharingStarted.Lazily, emptyMap())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index fedd63b..8fa33ee7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -54,12 +54,33 @@
) {
override fun start() {
- listenForAodToLockscreenOrOccluded()
+ listenForAodToLockscreen()
+ listenForAodToPrimaryBouncer()
listenForAodToGone()
+ listenForAodToOccluded()
listenForTransitionToCamera(scope, keyguardInteractor)
}
- private fun listenForAodToLockscreenOrOccluded() {
+ /**
+ * There are cases where the transition to AOD begins but never completes, such as tapping power
+ * during an incoming phone call when unlocked. In this case, GONE->AOD should be interrupted to
+ * run AOD->OCCLUDED.
+ */
+ private fun listenForAodToOccluded() {
+ scope.launch {
+ keyguardInteractor.isKeyguardOccluded.sample(startedKeyguardState, ::Pair).collect {
+ (isOccluded, startedKeyguardState) ->
+ if (isOccluded && startedKeyguardState == KeyguardState.AOD) {
+ startTransitionTo(
+ toState = KeyguardState.OCCLUDED,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ }
+ }
+ }
+ }
+
+ private fun listenForAodToLockscreen() {
scope.launch {
keyguardInteractor
.dozeTransitionTo(DozeStateModel.FINISH)
@@ -72,20 +93,15 @@
::toTriple
)
.collect { (_, lastStartedStep, occluded) ->
- if (lastStartedStep.to == KeyguardState.AOD) {
- val toState =
- if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN
+ if (lastStartedStep.to == KeyguardState.AOD && !occluded) {
val modeOnCanceled =
- if (
- toState == KeyguardState.LOCKSCREEN &&
- lastStartedStep.from == KeyguardState.LOCKSCREEN
- ) {
+ if (lastStartedStep.from == KeyguardState.LOCKSCREEN) {
TransitionModeOnCanceled.REVERSE
} else {
TransitionModeOnCanceled.LAST_VALUE
}
startTransitionTo(
- toState = toState,
+ toState = KeyguardState.LOCKSCREEN,
modeOnCanceled = modeOnCanceled,
)
}
@@ -93,11 +109,26 @@
}
}
+ /**
+ * If there is a biometric lockout and FPS is tapped while on AOD, it should go directly to the
+ * PRIMARY_BOUNCER.
+ */
+ private fun listenForAodToPrimaryBouncer() {
+ scope.launch {
+ keyguardInteractor.primaryBouncerShowing
+ .sample(startedKeyguardTransitionStep, ::Pair)
+ .collect { (isBouncerShowing, lastStartedTransitionStep) ->
+ if (isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.AOD) {
+ startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
+ }
+ }
+ }
+ }
+
private fun listenForAodToGone() {
scope.launch {
keyguardInteractor.biometricUnlockState.sample(finishedKeyguardState, ::Pair).collect {
- pair ->
- val (biometricUnlockState, keyguardState) = pair
+ (biometricUnlockState, keyguardState) ->
if (keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)) {
startTransitionTo(KeyguardState.GONE)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
index 2a68f26..0c0eb8a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
@@ -24,6 +24,7 @@
import androidx.constraintlayout.widget.ConstraintSet.LEFT
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.RIGHT
+import androidx.constraintlayout.widget.ConstraintSet.VISIBILITY_MODE_IGNORE
import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
@@ -96,6 +97,11 @@
constrainHeight(R.id.end_button, height)
connect(R.id.end_button, RIGHT, PARENT_ID, RIGHT, horizontalOffsetMargin)
connect(R.id.end_button, BOTTOM, PARENT_ID, BOTTOM, verticalOffsetMargin)
+
+ // The constraint set visibility for start and end button are default visible, set to
+ // ignore so the view's own initial visibility (invisible) is used
+ setVisibilityMode(R.id.start_button, VISIBILITY_MODE_IGNORE)
+ setVisibilityMode(R.id.end_button, VISIBILITY_MODE_IGNORE)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyboardLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyboardLog.kt
new file mode 100644
index 0000000..5910701
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyboardLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for keyboard-related functionality. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class KeyboardLog
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 24cb8ff..3e00940 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -603,6 +603,14 @@
return factory.create("BluetoothTileDialogLog", 50);
}
+ /** Provides a {@link LogBuffer} for the keyboard functionalities. */
+ @Provides
+ @SysUISingleton
+ @KeyboardLog
+ public static LogBuffer provideKeyboardLogBuffer(LogBufferFactory factory) {
+ return factory.create("KeyboardLog", 50);
+ }
+
/** Provides a {@link LogBuffer} for {@link PackageChangeRepository} */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
index a6cccf1..e2959fe 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
@@ -24,7 +24,9 @@
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.init.NotificationsController
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.policy.HeadsUpManager
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -44,6 +46,7 @@
private val keyguardRepository: KeyguardRepository,
private val headsUpManager: HeadsUpManager,
private val powerInteractor: PowerInteractor,
+ private val activeNotificationsInteractor: ActiveNotificationsInteractor,
) : CoreStartable {
private var notificationPresenter: NotificationPresenter? = null
@@ -117,6 +120,14 @@
return if (headsUpManager.hasPinnedHeadsUp() && isNotifPresenterFullyCollapsed) {
1
} else {
+ getActiveNotificationsCount()
+ }
+ }
+
+ private fun getActiveNotificationsCount(): Int {
+ return if (NotificationsLiveDataStoreRefactor.isEnabled) {
+ activeNotificationsInteractor.allNotificationsCountValue
+ } else {
notificationsController?.getActiveNotificationsCount() ?: 0
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index ab08f66..a755805 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
@@ -37,6 +37,7 @@
/** The flag description -- not an aconfig flag name */
const val DESCRIPTION = "SceneContainerFlag"
+ @JvmStatic
inline val isEnabled
get() =
SCENE_CONTAINER_ENABLED && // mainStaticFlag
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index 4a839b8..93cfc5d 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -85,12 +85,13 @@
view.addView(
ComposeFacade.createSceneContainerView(
- scope = this,
- context = view.context,
- viewModel = viewModel,
- windowInsets = windowInsets,
- sceneByKey = sortedSceneByKey,
- )
+ scope = this,
+ context = view.context,
+ viewModel = viewModel,
+ windowInsets = windowInsets,
+ sceneByKey = sortedSceneByKey,
+ )
+ .also { it.id = R.id.scene_container_root_composable }
)
val legacyView = view.requireViewById<View>(R.id.legacy_window_root)
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt
new file mode 100644
index 0000000..b09bfe2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.settings
+
+import android.annotation.UserIdInt
+import android.content.Context
+import android.content.SharedPreferences
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Extension functions for [UserFileManager]. */
+object UserFileManagerExt {
+
+ /** Returns a flow of [Unit] that is invoked each time the shared preference is updated. */
+ fun UserFileManager.observeSharedPreferences(
+ fileName: String,
+ @Context.PreferencesMode mode: Int,
+ @UserIdInt userId: Int
+ ): Flow<Unit> = conflatedCallbackFlow {
+ val sharedPrefs = getSharedPreferences(fileName, mode, userId)
+
+ val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> trySend(Unit) }
+
+ sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
+ awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
index 5180bab..b22e9fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -68,7 +68,8 @@
/**
* The same as [allNotificationsCount], but without flows, for easy access in synchronous code.
*/
- val allNotificationsCountValue: Int = repository.activeNotifications.value.individuals.size
+ val allNotificationsCountValue: Int
+ get() = repository.activeNotifications.value.individuals.size
/** Are any notifications being actively presented in the notification stack? */
val areAnyNotificationsPresent: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index ac49960..1677418 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -18,7 +18,6 @@
import android.service.notification.StatusBarNotification
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
import com.android.systemui.people.widget.PeopleSpaceWidgetManager
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
import com.android.systemui.statusbar.NotificationListener
@@ -72,7 +71,6 @@
private val animatedImageNotificationManager: AnimatedImageNotificationManager,
private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
private val bubblesOptional: Optional<Bubbles>,
- private val featureFlags: FeatureFlags
) : NotificationsController {
override fun initialize(
@@ -136,5 +134,8 @@
}
}
- override fun getActiveNotificationsCount(): Int = notifLiveDataStore.activeNotifCount.value
+ override fun getActiveNotificationsCount(): Int {
+ NotificationsLiveDataStoreRefactor.assertInLegacyMode()
+ return notifLiveDataStore.activeNotifCount.value
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index d9c5108..c527bb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -26,9 +26,9 @@
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.dump.DumpManager;
+import com.android.systemui.res.R;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index e791a64..04db653 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -94,6 +94,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.TouchLogger;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
@@ -187,6 +188,7 @@
private int mOverflingDistance;
private float mMaxOverScroll;
private boolean mIsBeingDragged;
+ private boolean mSendingTouchesToSceneFramework;
private int mLastMotionY;
private int mDownX;
private int mActivePointerId = INVALID_POINTER;
@@ -1509,7 +1511,12 @@
*/
public void setExpandedHeight(float height) {
final boolean skipHeightUpdate = shouldSkipHeightUpdate();
- updateStackPosition();
+
+ // when scene framework is enabled, updateStackPosition is already called by
+ // updateTopPadding every time the stack moves, so skip it here to avoid flickering.
+ if (!SceneContainerFlag.isEnabled()) {
+ updateStackPosition();
+ }
if (!skipHeightUpdate) {
mExpandedHeight = height;
@@ -2450,6 +2457,7 @@
/* notificationStackScrollLayout= */ this, mMaxDisplayedNotifications,
shelfIntrinsicHeight);
mIntrinsicContentHeight = height;
+ mController.setIntrinsicContentHeight(mIntrinsicContentHeight);
// The topPadding can be bigger than the regular padding when qs is expanded, in that
// state the maxPanelHeight and the contentHeight should be bigger
@@ -3558,8 +3566,11 @@
@Override
public boolean onTouchEvent(MotionEvent ev) {
- if (mTouchHandler != null && mTouchHandler.onTouchEvent(ev)) {
- return true;
+ if (mTouchHandler != null) {
+ boolean touchHandled = mTouchHandler.onTouchEvent(ev);
+ if (SceneContainerFlag.isEnabled() || touchHandled) {
+ return touchHandled;
+ }
}
return super.onTouchEvent(ev);
@@ -3567,6 +3578,27 @@
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (SceneContainerFlag.isEnabled() && mIsBeingDragged) {
+ if (!mSendingTouchesToSceneFramework) {
+ // if this is the first touch being sent to the scene framework,
+ // convert it into a synthetic DOWN event.
+ mSendingTouchesToSceneFramework = true;
+ MotionEvent downEvent = MotionEvent.obtain(ev);
+ downEvent.setAction(MotionEvent.ACTION_DOWN);
+ mController.sendTouchToSceneFramework(downEvent);
+ downEvent.recycle();
+ } else {
+ mController.sendTouchToSceneFramework(ev);
+ }
+
+ if (
+ ev.getActionMasked() == MotionEvent.ACTION_UP
+ || ev.getActionMasked() == MotionEvent.ACTION_CANCEL
+ ) {
+ setIsBeingDragged(false);
+ }
+ return false;
+ }
return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev));
}
@@ -3633,6 +3665,18 @@
return true;
}
+ // If the scene framework is enabled, ignore all non-move gestures if we are currently
+ // dragging - they should be dispatched to the scene framework. Move gestures should be let
+ // through to determine if we are still dragging or not.
+ if (
+ SceneContainerFlag.isEnabled()
+ && mIsBeingDragged
+ && action != MotionEvent.ACTION_MOVE
+ ) {
+ setIsBeingDragged(false);
+ return false;
+ }
+
switch (action) {
case MotionEvent.ACTION_DOWN: {
if (getChildCount() == 0 || !isInContentBounds(ev)) {
@@ -3676,6 +3720,11 @@
}
}
if (mIsBeingDragged) {
+ // Defer actual scrolling to the scene framework if enabled
+ if (SceneContainerFlag.isEnabled()) {
+ setIsBeingDragged(false);
+ return false;
+ }
// Scroll to follow the motion event
mLastMotionY = y;
float scrollAmount;
@@ -3770,9 +3819,8 @@
}
protected boolean isInsideQsHeader(MotionEvent ev) {
- if (mQsHeader == null) {
- Log.wtf(TAG, "qsHeader is null while NSSL is handling a touch");
- return false;
+ if (SceneContainerFlag.isEnabled()) {
+ return ev.getY() < mController.getPlaceholderTop();
}
mQsHeader.getBoundsOnScreen(mQsHeaderBound);
@@ -4026,9 +4074,16 @@
requestDisallowInterceptTouchEvent(true);
cancelLongPress();
resetExposedMenuView(true /* animate */, true /* force */);
+ } else {
+ mSendingTouchesToSceneFramework = false;
}
}
+ @VisibleForTesting
+ boolean getIsBeingDragged() {
+ return mIsBeingDragged;
+ }
+
public void requestDisallowLongPress() {
cancelLongPress();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index c0e0b73..6a66bb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -80,6 +80,9 @@
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.ui.view.WindowRootView;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.CommandQueue;
@@ -120,6 +123,7 @@
import com.android.systemui.statusbar.notification.row.NotificationGuts;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationSnooze;
+import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
@@ -145,6 +149,7 @@
import javax.inject.Inject;
import javax.inject.Named;
+import javax.inject.Provider;
/**
* Controller for {@link NotificationStackScrollLayout}.
@@ -181,6 +186,8 @@
private final NotificationRemoteInputManager mRemoteInputManager;
private final VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
private final ShadeController mShadeController;
+ private final Provider<WindowRootView> mWindowRootView;
+ private final NotificationStackAppearanceInteractor mStackAppearanceInteractor;
private final KeyguardMediaController mKeyguardMediaController;
private final SysuiStatusBarStateController mStatusBarStateController;
private final KeyguardBypassController mKeyguardBypassController;
@@ -689,6 +696,9 @@
SeenNotificationsInteractor seenNotificationsInteractor,
NotificationListViewBinder viewBinder,
ShadeController shadeController,
+ SceneContainerFlags sceneContainerFlags,
+ Provider<WindowRootView> windowRootView,
+ NotificationStackAppearanceInteractor stackAppearanceInteractor,
InteractionJankMonitor jankMonitor,
StackStateLogger stackLogger,
NotificationStackScrollLogger logger,
@@ -739,6 +749,8 @@
mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator;
mSeenNotificationsInteractor = seenNotificationsInteractor;
mShadeController = shadeController;
+ mWindowRootView = windowRootView;
+ mStackAppearanceInteractor = stackAppearanceInteractor;
mFeatureFlags = featureFlags;
mNotificationTargetsHelper = notificationTargetsHelper;
mSecureSettings = secureSettings;
@@ -1076,6 +1088,28 @@
return mView.getIntrinsicContentHeight();
}
+ /**
+ * Dispatch a touch to the scene container framework.
+ * TODO(b/316965302): Replace findViewById to avoid DFS
+ */
+ public void sendTouchToSceneFramework(MotionEvent ev) {
+ View sceneContainer = mWindowRootView.get()
+ .findViewById(R.id.scene_container_root_composable);
+ if (sceneContainer != null) {
+ sceneContainer.dispatchTouchEvent(ev);
+ }
+ }
+
+ /** Get the y-coordinate of the top bound of the stack. */
+ public float getPlaceholderTop() {
+ return mStackAppearanceInteractor.getStackBounds().getValue().getTop();
+ }
+
+ /** Set the intrinsic height of the stack content without additional padding. */
+ public void setIntrinsicContentHeight(float intrinsicContentHeight) {
+ mStackAppearanceInteractor.setIntrinsicContentHeight(intrinsicContentHeight);
+ }
+
public void setIntrinsicPadding(int intrinsicPadding) {
mView.setIntrinsicPadding(intrinsicPadding);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
index e78a694..aac3c28 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
@@ -30,4 +30,18 @@
/** The corner radius of the notification stack, in dp. */
val cornerRadiusDp = MutableStateFlow(32f)
+
+ /**
+ * The height in px of the contents of notification stack. Depending on the number of
+ * notifications, this can exceed the space available on screen to show notifications, at which
+ * point the notification stack should become scrollable.
+ */
+ val intrinsicContentHeight = MutableStateFlow(0f)
+
+ /**
+ * The y-coordinate in px of top of the contents of the notification stack. This value can be
+ * negative, if the stack is scrolled such that its top extends beyond the top edge of the
+ * screen.
+ */
+ val contentTop = MutableStateFlow(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index 61a4dfc..1dfde09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -34,12 +34,32 @@
/** The bounds of the notification stack in the current scene. */
val stackBounds: StateFlow<NotificationContainerBounds> = repository.stackBounds.asStateFlow()
+ /** The corner radius of the notification stack, in dp. */
+ val cornerRadiusDp: StateFlow<Float> = repository.cornerRadiusDp.asStateFlow()
+
+ /**
+ * The height in px of the contents of notification stack. Depending on the number of
+ * notifications, this can exceed the space available on screen to show notifications, at which
+ * point the notification stack should become scrollable.
+ */
+ val intrinsicContentHeight = repository.intrinsicContentHeight.asStateFlow()
+
+ /** The y-coordinate in px of top of the contents of the notification stack. */
+ val contentTop = repository.contentTop.asStateFlow()
+
/** Sets the position of the notification stack in the current scene. */
fun setStackBounds(bounds: NotificationContainerBounds) {
check(bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" }
repository.stackBounds.value = bounds
}
- /** The corner radius of the notification stack, in dp. */
- val cornerRadiusDp: StateFlow<Float> = repository.cornerRadiusDp.asStateFlow()
+ /** Sets the height of the contents of the notification stack. */
+ fun setIntrinsicContentHeight(height: Float) {
+ repository.intrinsicContentHeight.value = height
+ }
+
+ /** Sets the y-coord in px of the top of the contents of the notification stack. */
+ fun setContentTop(startY: Float) {
+ repository.contentTop.value = startY
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
index a9b542d..ed15f55 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
@@ -25,6 +25,7 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel
+import kotlin.math.pow
import kotlin.math.roundToInt
import kotlinx.coroutines.launch
@@ -43,24 +44,28 @@
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
viewModel.stackBounds.collect { bounds ->
- controller.updateTopPadding(
- bounds.top,
- controller.isAddOrRemoveAnimationPending
- )
controller.setRoundedClippingBounds(
- it.left,
- it.top,
- it.right,
- it.bottom,
+ bounds.left.roundToInt(),
+ bounds.top.roundToInt(),
+ bounds.right.roundToInt(),
+ bounds.bottom.roundToInt(),
viewModel.cornerRadiusDp.value.dpToPx(context),
viewModel.cornerRadiusDp.value.dpToPx(context),
)
}
}
+
+ launch {
+ viewModel.contentTop.collect {
+ controller.updateTopPadding(it, controller.isAddOrRemoveAnimationPending)
+ }
+ }
+
launch {
viewModel.expandFraction.collect { expandFraction ->
ambientState.expansionFraction = expandFraction
controller.expandedHeight = expandFraction * controller.view.height
+ controller.setMaxAlphaForExpansion(expandFraction.pow(0.75f))
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
index 834d3ff..74db583 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
@@ -41,4 +41,7 @@
/** The corner radius of the notification stack, in dp. */
val cornerRadiusDp: StateFlow<Float> = stackAppearanceInteractor.cornerRadiusDp
+
+ /** The y-coordinate in px of top of the contents of the notification stack. */
+ val contentTop: StateFlow<Float> = stackAppearanceInteractor.contentTop
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 9f22118..385f061 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -21,9 +21,11 @@
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
/**
@@ -35,6 +37,7 @@
@Inject
constructor(
private val interactor: NotificationStackAppearanceInteractor,
+ shadeInteractor: ShadeInteractor,
flags: SceneContainerFlags,
featureFlags: FeatureFlagsClassic,
) {
@@ -66,4 +69,22 @@
/** The corner radius of the placeholder, in dp. */
val cornerRadiusDp: StateFlow<Float> = interactor.cornerRadiusDp
+
+ /**
+ * The height in px of the contents of notification stack. Depending on the number of
+ * notifications, this can exceed the space available on screen to show notifications, at which
+ * point the notification stack should become scrollable.
+ */
+ val intrinsicContentHeight = interactor.intrinsicContentHeight
+
+ /**
+ * The amount [0-1] that the shade has been opened. At 0, the shade is closed; at 1, the shade
+ * is open.
+ */
+ val expandFraction: Flow<Float> = shadeInteractor.shadeExpansion
+
+ /** Sets the y-coord in px of the top of the contents of the notification stack. */
+ fun onContentTopChanged(padding: Float) {
+ interactor.setContentTop(padding)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index d8ef981..da6bfe8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -48,10 +48,10 @@
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
-import com.android.systemui.scene.SceneTestUtils;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -76,7 +76,7 @@
protected MockitoSession mStaticMockSession;
- protected final SceneTestUtils mSceneTestUtils = new SceneTestUtils(this);
+ protected final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
protected @Mock DeviceEntryInteractor mDeviceEntryInteractor;
protected @Mock LockIconView mLockIconView;
protected @Mock AnimatedStateListDrawable mIconDrawable;
@@ -175,7 +175,7 @@
mPrimaryBouncerInteractor,
mContext,
() -> mDeviceEntryInteractor,
- mSceneTestUtils.getFakeSceneContainerFlags()
+ mKosmos.getFakeSceneContainerFlags()
);
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
index 132bdb5..b0887ef 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
@@ -373,7 +373,7 @@
@Test
public void longPress_showBouncer_sceneContainerNotEnabled() {
init(/* useMigrationFlag= */ false);
- mSceneTestUtils.getFakeSceneContainerFlags().setEnabled(false);
+ mKosmos.getFakeSceneContainerFlags().setEnabled(false);
when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false);
// WHEN longPress
@@ -387,7 +387,7 @@
@Test
public void longPress_showBouncer() {
init(/* useMigrationFlag= */ false);
- mSceneTestUtils.getFakeSceneContainerFlags().setEnabled(true);
+ mKosmos.getFakeSceneContainerFlags().setEnabled(true);
when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false);
// WHEN longPress
@@ -401,7 +401,7 @@
@Test
public void longPress_falsingTriggered_doesNotShowBouncer() {
init(/* useMigrationFlag= */ false);
- mSceneTestUtils.getFakeSceneContainerFlags().setEnabled(true);
+ mKosmos.getFakeSceneContainerFlags().setEnabled(true);
when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(true);
// WHEN longPress
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index 68879a5..5e5273b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -55,8 +55,6 @@
import android.os.UserHandle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -120,10 +118,6 @@
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
-
@Spy
private SysuiTestableContext mSpyContext = getContext();
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index 7c626a1..e0c6bba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -44,6 +44,8 @@
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.util.concurrency.FakeExecutor
@@ -56,6 +58,7 @@
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import org.junit.Before
@@ -89,6 +92,9 @@
@Mock private lateinit var onBackInvokedDispatcher: WindowOnBackInvokedDispatcher
@Mock private lateinit var iStatusBarService: IStatusBarService
@Mock private lateinit var headsUpManager: HeadsUpManager
+ private val activeNotificationsRepository = ActiveNotificationListRepository()
+ private val activeNotificationsInteractor =
+ ActiveNotificationsInteractor(activeNotificationsRepository, StandardTestDispatcher())
private val keyguardRepository = FakeKeyguardRepository()
private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy {
@@ -98,6 +104,7 @@
keyguardRepository,
headsUpManager,
powerInteractor,
+ activeNotificationsInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 0ee0939..43f7c60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.biometrics
import android.app.admin.DevicePolicyManager
+import android.content.pm.PackageManager
import android.hardware.biometrics.BiometricAuthenticator
import android.hardware.biometrics.BiometricConstants
import android.hardware.biometrics.BiometricManager
@@ -79,6 +80,8 @@
import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
+private const val OP_PACKAGE_NAME = "biometric.testapp"
+
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
@@ -109,6 +112,8 @@
lateinit var authController: AuthController
@Mock
lateinit var selectedUserInteractor: SelectedUserInteractor
+ @Mock
+ private lateinit var packageManager: PackageManager
private val testScope = TestScope(StandardTestDispatcher())
private val fakeExecutor = FakeExecutor(FakeSystemClock())
@@ -134,6 +139,7 @@
private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
+ private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
private var authContainer: TestAuthContainerView? = null
@@ -156,6 +162,9 @@
selectedUserInteractor,
testScope.backgroundScope,
)
+ // Set up default logo icon
+ whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon)
+ context.setMockPackageManager(packageManager)
}
@After
@@ -533,6 +542,7 @@
mPromptInfo = PromptInfo().apply {
this.authenticators = authenticators
}
+ mOpPackageName = OP_PACKAGE_NAME
},
testScope.backgroundScope,
fingerprintProps,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
index ec7ce63..b39e09d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
@@ -43,6 +43,7 @@
private const val USER_ID = 9
private const val CHALLENGE = 90L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -102,7 +103,8 @@
PromptInfo().apply { isConfirmationRequested = case },
USER_ID,
CHALLENGE,
- PromptKind.Biometric()
+ PromptKind.Biometric(),
+ OP_PACKAGE_NAME
)
assertThat(isConfirmationRequired).isEqualTo(case)
@@ -120,7 +122,8 @@
PromptInfo().apply { isConfirmationRequested = case },
USER_ID,
CHALLENGE,
- PromptKind.Biometric()
+ PromptKind.Biometric(),
+ OP_PACKAGE_NAME
)
assertThat(isConfirmationRequired).isTrue()
@@ -133,17 +136,19 @@
val kind = PromptKind.Pin
val promptInfo = PromptInfo()
- repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind)
+ repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind, OP_PACKAGE_NAME)
assertThat(repository.kind.value).isEqualTo(kind)
assertThat(repository.userId.value).isEqualTo(USER_ID)
assertThat(repository.challenge.value).isEqualTo(CHALLENGE)
assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo)
+ assertThat(repository.opPackageName.value).isEqualTo(OP_PACKAGE_NAME)
repository.unsetPrompt()
assertThat(repository.promptInfo.value).isNull()
assertThat(repository.userId.value).isNull()
assertThat(repository.challenge.value).isNull()
+ assertThat(repository.opPackageName.value).isNull()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
index dcefea2..8a46c0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
@@ -30,6 +30,7 @@
private const val USER_ID = 22
private const val OPERATION_ID = 100L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -114,7 +115,8 @@
},
kind = kind,
userId = USER_ID,
- challenge = OPERATION_ID
+ challenge = OPERATION_ID,
+ opPackageName = OP_PACKAGE_NAME
)
assertThat(prompt?.title).isEqualTo(title)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
index f15b738..52b4275 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
@@ -51,6 +51,7 @@
private const val USER_ID = 8
private const val CHALLENGE = 999L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -113,13 +114,20 @@
assertThat(currentPrompt).isNull()
- interactor.useBiometricsForAuthentication(info, USER_ID, CHALLENGE, modalities)
+ interactor.useBiometricsForAuthentication(
+ info,
+ USER_ID,
+ CHALLENGE,
+ modalities,
+ OP_PACKAGE_NAME
+ )
assertThat(currentPrompt).isNotNull()
assertThat(currentPrompt?.title).isEqualTo(TITLE)
assertThat(currentPrompt?.description).isEqualTo(DESCRIPTION)
assertThat(currentPrompt?.subtitle).isEqualTo(SUBTITLE)
assertThat(currentPrompt?.negativeButtonText).isEqualTo(NEGATIVE_TEXT)
+ assertThat(currentPrompt?.opPackageName).isEqualTo(OP_PACKAGE_NAME)
if (allowCredentialFallback) {
assertThat(credentialKind).isSameInstanceAs(PromptKind.Password)
@@ -167,7 +175,7 @@
assertThat(currentPrompt).isNull()
- interactor.useCredentialsForAuthentication(info, kind, USER_ID, CHALLENGE)
+ interactor.useCredentialsForAuthentication(info, kind, USER_ID, CHALLENGE, OP_PACKAGE_NAME)
// not using biometrics, should be null with no fallback option
assertThat(currentPrompt).isNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
index a57b890..a46167a42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.biometrics.domain.model
+import android.graphics.Bitmap
import android.hardware.biometrics.PromptContentItemBulletedText
import android.hardware.biometrics.PromptVerticalListContentView
import androidx.test.filters.SmallTest
@@ -8,6 +9,7 @@
import com.android.systemui.biometrics.promptInfo
import com.android.systemui.biometrics.shared.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricUserInfo
+import com.android.systemui.res.R
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -15,6 +17,7 @@
private const val USER_ID = 2
private const val OPERATION_ID = 8L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
@SmallTest
@RunWith(JUnit4::class)
@@ -22,6 +25,7 @@
@Test
fun biometricRequestFromPromptInfo() {
+ val logoRes = R.drawable.ic_cake
val title = "what"
val subtitle = "a"
val description = "request"
@@ -36,6 +40,7 @@
val request =
BiometricPromptRequest.Biometric(
promptInfo(
+ logoRes = logoRes,
title = title,
subtitle = subtitle,
description = description,
@@ -44,8 +49,10 @@
BiometricUserInfo(USER_ID),
BiometricOperationInfo(OPERATION_ID),
BiometricModalities(fingerprintProperties = fpPros),
+ OP_PACKAGE_NAME,
)
+ assertThat(request.logoRes).isEqualTo(logoRes)
assertThat(request.title).isEqualTo(title)
assertThat(request.subtitle).isEqualTo(subtitle)
assertThat(request.description).isEqualTo(description)
@@ -57,6 +64,23 @@
}
@Test
+ fun biometricRequestLogoBitmapFromPromptInfo() {
+ val logoBitmap = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888)
+ val fpPros = fingerprintSensorPropertiesInternal().first()
+ val request =
+ BiometricPromptRequest.Biometric(
+ promptInfo(
+ logoBitmap = logoBitmap,
+ ),
+ BiometricUserInfo(USER_ID),
+ BiometricOperationInfo(OPERATION_ID),
+ BiometricModalities(fingerprintProperties = fpPros),
+ OP_PACKAGE_NAME,
+ )
+ assertThat(request.logoBitmap).isEqualTo(logoBitmap)
+ }
+
+ @Test
fun credentialRequestFromPromptInfo() {
val title = "what"
val subtitle = "a"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 3944054..3888f2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -16,8 +16,11 @@
package com.android.systemui.biometrics.ui.viewmodel
+import android.content.pm.PackageManager
import android.content.res.Configuration
+import android.graphics.Bitmap
import android.graphics.Point
+import android.graphics.drawable.BitmapDrawable
import android.hardware.biometrics.PromptContentItemBulletedText
import android.hardware.biometrics.PromptContentView
import android.hardware.biometrics.PromptInfo
@@ -76,6 +79,7 @@
private const val USER_ID = 4
private const val CHALLENGE = 2L
private const val DELAY = 1000L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -88,9 +92,14 @@
@Mock private lateinit var authController: AuthController
@Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
@Mock private lateinit var udfpsUtils: UdfpsUtils
+ @Mock private lateinit var packageManager: PackageManager
private val fakeExecutor = FakeExecutor(FakeSystemClock())
private val testScope = TestScope()
+ private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
+ private val logoResFromApp = R.drawable.ic_cake
+ private val logoFromApp = context.getDrawable(logoResFromApp)
+ private val logoBitmapFromApp = Bitmap.createBitmap(400, 400, Bitmap.Config.RGB_565)
private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository
private lateinit var promptRepository: FakePromptRepository
@@ -153,6 +162,12 @@
udfpsUtils
)
iconViewModel = viewModel.iconViewModel
+
+ // Set up default logo icon and app customized icon
+ whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon)
+ context.setMockPackageManager(packageManager)
+ val resources = context.getOrCreateTestableResources()
+ resources.addOverride(logoResFromApp, logoFromApp)
}
@Test
@@ -1227,6 +1242,26 @@
assertThat(contentView).isNull()
}
+ @Test
+ fun defaultLogoIfNoLogoSet() = runGenericTest {
+ val logo by collectLastValue(viewModel.logo)
+ assertThat(logo).isEqualTo(defaultLogoIcon)
+ }
+
+ @Test
+ fun logoResSetByApp() =
+ runGenericTest(logoRes = logoResFromApp) {
+ val logo by collectLastValue(viewModel.logo)
+ assertThat(logo).isEqualTo(logoFromApp)
+ }
+
+ @Test
+ fun logoBitmapSetByApp() =
+ runGenericTest(logoBitmap = logoBitmapFromApp) {
+ val logo by collectLastValue(viewModel.logo)
+ assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp)
+ }
+
/** Asserts that the selected buttons are visible now. */
private suspend fun TestScope.assertButtonsVisible(
tryAgain: Boolean = false,
@@ -1248,6 +1283,8 @@
allowCredentialFallback: Boolean = false,
description: String? = null,
contentView: PromptContentView? = null,
+ logoRes: Int = -1,
+ logoBitmap: Bitmap? = null,
block: suspend TestScope.() -> Unit
) {
selector.initializePrompt(
@@ -1257,6 +1294,8 @@
face = testCase.face,
descriptionFromApp = description,
contentViewFromApp = contentView,
+ logoResFromApp = logoRes,
+ logoBitmapFromApp = logoBitmap,
)
// put the view model in the initial authenticating state, unless explicitly skipped
@@ -1434,9 +1473,13 @@
allowCredentialFallback: Boolean = false,
descriptionFromApp: String? = null,
contentViewFromApp: PromptContentView? = null,
+ logoResFromApp: Int = -1,
+ logoBitmapFromApp: Bitmap? = null,
) {
val info =
PromptInfo().apply {
+ logoRes = logoResFromApp
+ logoBitmap = logoBitmapFromApp
title = "t"
subtitle = "s"
description = descriptionFromApp
@@ -1445,11 +1488,13 @@
isDeviceCredentialAllowed = allowCredentialFallback
isConfirmationRequested = requireConfirmation
}
+
useBiometricsForAuthentication(
info,
USER_ID,
CHALLENGE,
BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
+ OP_PACKAGE_NAME,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
index 44c57f3..134c40d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
@@ -3,8 +3,9 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.SystemClock
@@ -23,8 +24,8 @@
@Mock private lateinit var systemClock: SystemClock
@Mock private lateinit var bouncerLogger: TableLogBuffer
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
lateinit var underTest: KeyguardBouncerRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
new file mode 100644
index 0000000..d397fc2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard.stickykeys.ui.viewmodel
+
+import android.hardware.input.InputManager
+import android.hardware.input.StickyModifierState
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
+import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
+import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepositoryImpl
+import com.android.systemui.keyboard.stickykeys.shared.model.Locked
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+
+@SmallTest
+@RunWith(JUnit4::class)
+class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
+
+ private val dispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+ private lateinit var viewModel: StickyKeysIndicatorViewModel
+ private val inputManager = mock<InputManager>()
+ private val keyboardRepository = FakeKeyboardRepository()
+ private val captor =
+ ArgumentCaptor.forClass(InputManager.StickyModifierStateListener::class.java)
+
+ @Before
+ fun setup() {
+ val stickyKeysRepository = StickyKeysRepositoryImpl(
+ inputManager,
+ dispatcher,
+ mock<StickyKeysLogger>()
+ )
+ viewModel =
+ StickyKeysIndicatorViewModel(
+ stickyKeysRepository = stickyKeysRepository,
+ keyboardRepository = keyboardRepository,
+ applicationScope = testScope.backgroundScope,
+ )
+ }
+
+ @Test
+ fun startsListeningToStickyKeysOnlyWhenKeyboardIsConnected() {
+ testScope.runTest {
+ collectLastValue(viewModel.indicatorContent)
+ runCurrent()
+ verifyZeroInteractions(inputManager)
+
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ runCurrent()
+
+ verify(inputManager)
+ .registerStickyModifierStateListener(
+ any(),
+ any(InputManager.StickyModifierStateListener::class.java)
+ )
+ }
+ }
+
+ @Test
+ fun stopsListeningToStickyKeysWhenKeyboardDisconnects() {
+ testScope.runTest {
+ collectLastValue(viewModel.indicatorContent)
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ runCurrent()
+
+ keyboardRepository.setIsAnyKeyboardConnected(false)
+ runCurrent()
+
+ verify(inputManager).unregisterStickyModifierStateListener(any())
+ }
+ }
+
+ @Test
+ fun emitsStickyKeysListWhenStickyKeyIsPressed() {
+ testScope.runTest {
+ val stickyKeys by collectLastValue(viewModel.indicatorContent)
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+
+ setStickyKeys(mapOf(ALT to false))
+
+ assertThat(stickyKeys).isEqualTo(mapOf(ALT to Locked(false)))
+ }
+ }
+
+ @Test
+ fun emitsEmptyListWhenNoStickyKeysAreActive() {
+ testScope.runTest {
+ val stickyKeys by collectLastValue(viewModel.indicatorContent)
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+
+ setStickyKeys(emptyMap())
+
+ assertThat(stickyKeys).isEqualTo(emptyMap<ModifierKey, Locked>())
+ }
+ }
+
+ @Test
+ fun passesAllStickyKeysToDialog() {
+ testScope.runTest {
+ val stickyKeys by collectLastValue(viewModel.indicatorContent)
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+
+ setStickyKeys(mapOf(
+ ALT to false,
+ META to false,
+ SHIFT to false))
+
+ assertThat(stickyKeys).isEqualTo(mapOf(
+ ALT to Locked(false),
+ META to Locked(false),
+ SHIFT to Locked(false),
+ ))
+ }
+ }
+
+ @Test
+ fun showsOnlyLockedStateIfKeyIsStickyAndLocked() {
+ testScope.runTest {
+ val stickyKeys by collectLastValue(viewModel.indicatorContent)
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+
+ setStickyKeys(mapOf(
+ ALT to false,
+ ALT to true))
+
+ assertThat(stickyKeys).isEqualTo(mapOf(ALT to Locked(true)))
+ }
+ }
+
+ @Test
+ fun doesNotChangeOrderOfKeysIfTheyBecomeLocked() {
+ testScope.runTest {
+ val stickyKeys by collectLastValue(viewModel.indicatorContent)
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+
+ setStickyKeys(mapOf(
+ META to false,
+ SHIFT to false, // shift is sticky but not locked
+ CTRL to false))
+ val previousShiftIndex = stickyKeys?.toList()?.indexOf(SHIFT to Locked(false))
+
+ setStickyKeys(mapOf(
+ SHIFT to false,
+ SHIFT to true, // shift is now locked
+ META to false,
+ CTRL to false))
+ assertThat(stickyKeys?.toList()?.indexOf(SHIFT to Locked(true)))
+ .isEqualTo(previousShiftIndex)
+ }
+ }
+
+ private fun TestScope.setStickyKeys(keys: Map<ModifierKey, Boolean>) {
+ runCurrent()
+ verify(inputManager).registerStickyModifierStateListener(any(), captor.capture())
+ captor.value.onStickyModifierStateChanged(TestStickyModifierState(keys))
+ runCurrent()
+ }
+
+ private class TestStickyModifierState(private val keys: Map<ModifierKey, Boolean>) :
+ StickyModifierState() {
+
+ private fun isOn(key: ModifierKey) = keys.any { it.key == key && !it.value }
+ private fun isLocked(key: ModifierKey) = keys.any { it.key == key && it.value }
+
+ override fun isAltGrModifierLocked() = isLocked(ALT_GR)
+ override fun isAltGrModifierOn() = isOn(ALT_GR)
+ override fun isAltModifierLocked() = isLocked(ALT)
+ override fun isAltModifierOn() = isOn(ALT)
+ override fun isCtrlModifierLocked() = isLocked(CTRL)
+ override fun isCtrlModifierOn() = isOn(CTRL)
+ override fun isMetaModifierLocked() = isLocked(META)
+ override fun isMetaModifierOn() = isOn(META)
+ override fun isShiftModifierLocked() = isLocked(SHIFT)
+ override fun isShiftModifierOn() = isOn(SHIFT)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 0ea4e9f..8b6611f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -1342,14 +1342,8 @@
runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.AOD)
runCurrent()
- // WHEN the keyguard is occluded and aod ends
+ // WHEN the keyguard is occluded
keyguardRepository.setKeyguardOccluded(true)
- keyguardRepository.setDozeTransitionModel(
- DozeTransitionModel(
- from = DozeStateModel.DOZE_AOD,
- to = DozeStateModel.FINISH,
- )
- )
runCurrent()
val info =
@@ -1366,6 +1360,30 @@
}
@Test
+ fun aodToPrimaryBouncer() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to AOD
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.AOD)
+ runCurrent()
+
+ // WHEN the primary bouncer is set to show
+ bouncerRepository.setPrimaryShow(true)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ // THEN a transition to OCCLUDED should occur
+ assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.AOD)
+ assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun lockscreenToOccluded_fromCameraGesture() =
testScope.runTest {
// GIVEN a prior transition has run to LOCKSCREEN
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 49579f6..b3e386e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -113,6 +113,7 @@
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.KeyguardMediaController;
import com.android.systemui.media.controls.ui.MediaHierarchyManager;
@@ -125,7 +126,6 @@
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.qs.QSFragmentLegacy;
import com.android.systemui.res.R;
-import com.android.systemui.scene.SceneTestUtils;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.data.repository.ShadeAnimationRepository;
@@ -355,8 +355,8 @@
protected FakeKeyguardRepository mFakeKeyguardRepository;
protected KeyguardInteractor mKeyguardInteractor;
protected ShadeAnimationInteractor mShadeAnimationInteractor;
- protected SceneTestUtils mUtils = new SceneTestUtils(this);
- protected TestScope mTestScope = mUtils.getTestScope();
+ protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+ protected TestScope mTestScope = mKosmos.getTestScope();
protected ShadeInteractor mShadeInteractor;
protected PowerInteractor mPowerInteractor;
protected NotificationPanelViewController.TouchHandler mTouchHandler;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 971c8a3..a894f87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -73,11 +73,11 @@
import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.res.R;
import com.android.systemui.scene.FakeWindowRootViewComponent;
-import com.android.systemui.scene.SceneTestUtils;
import com.android.systemui.scene.data.repository.SceneContainerRepository;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
@@ -150,8 +150,8 @@
private final Executor mMainExecutor = MoreExecutors.directExecutor();
private final Executor mBackgroundExecutor = MoreExecutors.directExecutor();
- private final SceneTestUtils mUtils = new SceneTestUtils(this);
- private final TestScope mTestScope = mUtils.getTestScope();
+ private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+ private final TestScope mTestScope = mKosmos.getTestScope();
private ShadeInteractor mShadeInteractor;
private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
@@ -178,15 +178,15 @@
FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
FakeShadeRepository shadeRepository = new FakeShadeRepository();
- mScreenOffAnimationController = mUtils.getScreenOffAnimationController();
- mStatusBarStateController = spy(mUtils.getStatusBarStateController());
- PowerInteractor powerInteractor = mUtils.powerInteractor();
+ mScreenOffAnimationController = mKosmos.getScreenOffAnimationController();
+ mStatusBarStateController = spy(mKosmos.getStatusBarStateController());
+ PowerInteractor powerInteractor = mKosmos.getPowerInteractor();
SceneInteractor sceneInteractor = new SceneInteractor(
mTestScope.getBackgroundScope(),
new SceneContainerRepository(
mTestScope.getBackgroundScope(),
- mUtils.fakeSceneContainerConfig()),
+ mKosmos.getFakeSceneContainerConfig()),
powerInteractor,
mock(SceneLogger.class));
@@ -219,8 +219,8 @@
keyguardTransitionRepository,
keyguardTransitionInteractor,
mTestScope.getBackgroundScope(),
- mUtils.getTestDispatcher(),
- mUtils.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
keyguardInteractor,
featureFlags,
shadeRepository,
@@ -245,8 +245,8 @@
keyguardTransitionRepository,
keyguardTransitionInteractor,
mTestScope.getBackgroundScope(),
- mUtils.getTestDispatcher(),
- mUtils.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
keyguardInteractor,
featureFlags,
mKeyguardSecurityModel,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index ca68fd8..cbd4d2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -61,6 +61,7 @@
import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.MediaHierarchyManager;
import com.android.systemui.plugins.FalsingManager;
@@ -68,7 +69,6 @@
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.qs.QSFragmentLegacy;
import com.android.systemui.res.R;
-import com.android.systemui.scene.SceneTestUtils;
import com.android.systemui.scene.data.repository.SceneContainerRepository;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
@@ -131,8 +131,8 @@
protected QuickSettingsController mQsController;
- protected SceneTestUtils mUtils = new SceneTestUtils(this);
- protected TestScope mTestScope = mUtils.getTestScope();
+ protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+ protected TestScope mTestScope = mKosmos.getTestScope();
@Mock protected Resources mResources;
@Mock protected KeyguardBottomAreaView mQsFrame;
@@ -203,8 +203,8 @@
public void setup() {
MockitoAnnotations.initMocks(this);
when(mPanelViewControllerLazy.get()).thenReturn(mNotificationPanelViewController);
- mStatusBarStateController = mUtils.getStatusBarStateController();
- mInteractionJankMonitor = mUtils.getInteractionJankMonitor();
+ mStatusBarStateController = mKosmos.getStatusBarStateController();
+ mInteractionJankMonitor = mKosmos.getInteractionJankMonitor();
FakeDeviceProvisioningRepository deviceProvisioningRepository =
new FakeDeviceProvisioningRepository();
@@ -212,13 +212,13 @@
FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
- PowerInteractor powerInteractor = mUtils.powerInteractor();
+ PowerInteractor powerInteractor = mKosmos.getPowerInteractor();
SceneInteractor sceneInteractor = new SceneInteractor(
mTestScope.getBackgroundScope(),
new SceneContainerRepository(
mTestScope.getBackgroundScope(),
- mUtils.fakeSceneContainerConfig()),
+ mKosmos.getFakeSceneContainerConfig()),
powerInteractor,
mock(SceneLogger.class));
@@ -250,8 +250,8 @@
keyguardTransitionRepository,
keyguardTransitionInteractor,
mTestScope.getBackgroundScope(),
- mUtils.getTestDispatcher(),
- mUtils.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
keyguardInteractor,
featureFlags,
mShadeRepository,
@@ -276,8 +276,8 @@
keyguardTransitionRepository,
keyguardTransitionInteractor,
mTestScope.getBackgroundScope(),
- mUtils.getTestDispatcher(),
- mUtils.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
keyguardInteractor,
featureFlags,
mock(KeyguardSecurityModel.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index 215f8b1..c4911a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -33,6 +33,8 @@
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.row.NotificationGutsManager
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -45,6 +47,7 @@
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
+import kotlinx.coroutines.test.StandardTestDispatcher
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -59,6 +62,8 @@
@SmallTest
class ShadeControllerImplTest : SysuiTestCase() {
private val executor = FakeExecutor(FakeSystemClock())
+ private val testDispatcher = StandardTestDispatcher()
+ private val activeNotificationsRepository = ActiveNotificationListRepository()
@Mock private lateinit var commandQueue: CommandQueue
@Mock private lateinit var keyguardStateController: KeyguardStateController
@@ -84,6 +89,7 @@
FakeKeyguardRepository(),
headsUpManager,
PowerInteractorFactory.create().powerInteractor,
+ ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher)
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 4a365b9..05e866e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -41,10 +41,12 @@
import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.shade.data.repository.FakeShadeRepository
@@ -56,6 +58,7 @@
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.flow.emptyFlow
import org.junit.Assert.assertEquals
@@ -71,17 +74,17 @@
import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class StatusBarStateControllerImplTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val testDispatcher = utils.testDispatcher
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val testDispatcher = kosmos.testDispatcher
private lateinit var shadeInteractor: ShadeInteractor
private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
private lateinit var fromPrimaryBouncerTransitionInteractor:
@@ -131,7 +134,7 @@
FakeKeyguardBouncerRepository(),
ConfigurationInteractor(configurationRepository),
shadeRepository,
- utils::sceneInteractor
+ { kosmos.sceneInteractor },
)
val keyguardTransitionInteractor =
KeyguardTransitionInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index 168e782..ff02ef3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -28,6 +28,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
+
import android.app.Notification;
import android.os.Handler;
import android.os.Looper;
@@ -56,6 +58,8 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
import com.android.systemui.statusbar.notification.logging.nano.Notifications;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -110,6 +114,11 @@
private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
private final PowerInteractor mPowerInteractor =
PowerInteractorFactory.create().getPowerInteractor();
+ private final ActiveNotificationListRepository mActiveNotificationListRepository =
+ new ActiveNotificationListRepository();
+ private final ActiveNotificationsInteractor mActiveNotificationsInteractor =
+ new ActiveNotificationsInteractor(mActiveNotificationListRepository,
+ StandardTestDispatcher(null, null));
private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
@@ -123,7 +132,8 @@
new WindowRootViewVisibilityRepository(mBarService, mUiBgExecutor),
mKeyguardRepository,
mHeadsUpManager,
- mPowerInteractor);
+ mPowerInteractor,
+ mActiveNotificationsInteractor);
mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
mEntry = new NotificationEntryBuilder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 8a730cf..71613ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -42,6 +42,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
+
import android.app.INotificationManager;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -85,6 +87,8 @@
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -156,6 +160,12 @@
@Mock private UserManager mUserManager;
+ private final ActiveNotificationListRepository mActiveNotificationListRepository =
+ new ActiveNotificationListRepository();
+ private final ActiveNotificationsInteractor mActiveNotificationsInteractor =
+ new ActiveNotificationsInteractor(mActiveNotificationListRepository,
+ StandardTestDispatcher(null, null));
+
private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
@Before
@@ -171,7 +181,8 @@
new WindowRootViewVisibilityRepository(mBarService, mExecutor),
new FakeKeyguardRepository(),
mHeadsUpManager,
- PowerInteractorFactory.create().getPowerInteractor());
+ PowerInteractorFactory.create().getPowerInteractor(),
+ mActiveNotificationsInteractor);
mGutsManager = new NotificationGutsManager(
mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 88662b6..89f826b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -66,6 +66,8 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.ui.view.WindowRootView;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -91,6 +93,7 @@
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback;
+import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScrimController;
@@ -112,6 +115,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import javax.inject.Provider;
+
/**
* Tests for {@link NotificationStackScrollLayoutController}.
*/
@@ -153,6 +158,9 @@
@Mock private NotificationRemoteInputManager mRemoteInputManager;
@Mock private VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
@Mock private ShadeController mShadeController;
+ @Mock private SceneContainerFlags mSceneContainerFlags;
+ @Mock private Provider<WindowRootView> mWindowRootView;
+ @Mock private NotificationStackAppearanceInteractor mNotificationStackAppearanceInteractor;
@Mock private InteractionJankMonitor mJankMonitor;
private final StackStateLogger mStackLogger = new StackStateLogger(logcatLogBuffer(),
logcatLogBuffer());
@@ -724,6 +732,9 @@
mSeenNotificationsInteractor,
mViewBinder,
mShadeController,
+ mSceneContainerFlags,
+ mWindowRootView,
+ mNotificationStackAppearanceInteractor,
mJankMonitor,
mStackLogger,
mLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index a172120..4afcc8c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -20,6 +20,7 @@
import static android.view.WindowInsets.Type.ime;
import static com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION;
+import static com.android.systemui.Flags.FLAG_SCENE_CONTAINER;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.RUBBER_BAND_FACTOR_NORMAL;
@@ -37,6 +38,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
@@ -51,6 +53,7 @@
import android.graphics.Insets;
import android.graphics.Rect;
+import android.os.SystemClock;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
@@ -74,6 +77,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.EmptyShadeView;
@@ -93,11 +97,13 @@
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import org.junit.Assert;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -947,6 +953,78 @@
verify(runnable).run();
}
+ @Test
+ public void testDispatchTouchEvent_sceneContainerDisabled() {
+ Assume.assumeFalse(SceneContainerFlag.isEnabled());
+
+ MotionEvent event = MotionEvent.obtain(
+ SystemClock.uptimeMillis(),
+ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_MOVE,
+ 0,
+ 0,
+ 0
+ );
+
+ mStackScroller.dispatchTouchEvent(event);
+
+ verify(mStackScrollLayoutController, never()).sendTouchToSceneFramework(any());
+ }
+
+ @Test
+ public void testDispatchTouchEvent_sceneContainerEnabled() {
+ Assume.assumeTrue(SceneContainerFlag.isEnabled());
+ mStackScroller.setIsBeingDragged(true);
+
+ MotionEvent moveEvent = MotionEvent.obtain(
+ SystemClock.uptimeMillis(),
+ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_MOVE,
+ 0,
+ 0,
+ 0
+ );
+ MotionEvent syntheticDownEvent = moveEvent.copy();
+ syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN);
+ mStackScroller.dispatchTouchEvent(moveEvent);
+
+ verify(mStackScrollLayoutController).sendTouchToSceneFramework(argThat(
+ new MotionEventMatcher(syntheticDownEvent)));
+
+ mStackScroller.dispatchTouchEvent(moveEvent);
+
+ verify(mStackScrollLayoutController).sendTouchToSceneFramework(moveEvent);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCENE_CONTAINER)
+ public void testDispatchTouchEvent_sceneContainerEnabled_actionUp() {
+ Assume.assumeTrue(SceneContainerFlag.isEnabled());
+ mStackScroller.setIsBeingDragged(true);
+
+ MotionEvent upEvent = MotionEvent.obtain(
+ SystemClock.uptimeMillis(),
+ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_UP,
+ 0,
+ 0,
+ 0
+ );
+ MotionEvent syntheticDownEvent = upEvent.copy();
+ syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN);
+
+ mStackScroller.dispatchTouchEvent(upEvent);
+
+ verify(mStackScrollLayoutController, atLeastOnce()).sendTouchToSceneFramework(argThat(
+ new MotionEventMatcher(syntheticDownEvent)));
+
+ mStackScroller.dispatchTouchEvent(upEvent);
+
+ verify(mStackScrollLayoutController, atLeastOnce()).sendTouchToSceneFramework(argThat(
+ new MotionEventMatcher(upEvent)));
+ assertFalse(mStackScroller.getIsBeingDragged());
+ }
+
private void setBarStateForTest(int state) {
// Can't inject this through the listener or we end up on the actual implementation
// rather than the mock because the spy just coppied the anonymous inner /shruggie.
@@ -993,4 +1071,21 @@
/* metaState= */0
);
}
+
+ private static class MotionEventMatcher implements ArgumentMatcher<MotionEvent> {
+ private final MotionEvent mLeftEvent;
+
+ MotionEventMatcher(MotionEvent leftEvent) {
+ mLeftEvent = leftEvent;
+ }
+
+ @Override
+ public boolean matches(MotionEvent right) {
+ return mLeftEvent.getActionMasked() == right.getActionMasked()
+ && mLeftEvent.getDownTime() == right.getDownTime()
+ && mLeftEvent.getEventTime() == right.getEventTime()
+ && mLeftEvent.getX() == right.getX()
+ && mLeftEvent.getY() == right.getY();
+ }
+ }
}
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 91cbc32..7362e34 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
@@ -24,9 +24,9 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
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
@@ -35,6 +35,7 @@
import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.tuner.TunerService
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -60,8 +61,8 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class KeyguardBypassControllerTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private val featureFlags = FakeFeatureFlags()
private val shadeRepository = FakeShadeRepository()
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 5fa7f13e..2d120cd 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
@@ -60,10 +60,10 @@
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
import com.android.systemui.res.R;
-import com.android.systemui.scene.SceneTestUtils;
import com.android.systemui.shade.ShadeViewStateProvider;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.statusbar.CommandQueue;
@@ -149,7 +149,7 @@
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private final TestScope mTestScope = TestScopeProvider.getTestScope();
private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
- private final SceneTestUtils mSceneTestUtils = new SceneTestUtils(this);
+ private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
private KeyguardInteractor mKeyguardInteractor;
private KeyguardStatusBarViewModel mViewModel;
@@ -166,11 +166,11 @@
mKeyguardRepository,
mCommandQueue,
PowerInteractorFactory.create().getPowerInteractor(),
- mSceneTestUtils.getFakeSceneContainerFlags(),
+ mKosmos.getFakeSceneContainerFlags(),
new FakeKeyguardBouncerRepository(),
new ConfigurationInteractor(new FakeConfigurationRepository()),
new FakeShadeRepository(),
- () -> mSceneTestUtils.sceneInteractor());
+ () -> mKosmos.getSceneInteractor());
mViewModel =
new KeyguardStatusBarViewModel(
mTestScope.getBackgroundScope(),
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 5f9c096..ca0e526 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
@@ -25,20 +25,22 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
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.testKosmos
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
@@ -47,20 +49,20 @@
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
class KeyguardStatusBarViewModelTest : SysuiTestCase() {
- private val testScope = TestScope()
- private val sceneTestUtils = SceneTestUtils(this)
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private val keyguardRepository = FakeKeyguardRepository()
private val keyguardInteractor =
KeyguardInteractor(
keyguardRepository,
mock<CommandQueue>(),
PowerInteractorFactory.create().powerInteractor,
- sceneTestUtils.fakeSceneContainerFlags,
+ kosmos.fakeSceneContainerFlags,
FakeKeyguardBouncerRepository(),
ConfigurationInteractor(FakeConfigurationRepository()),
FakeShadeRepository(),
) {
- sceneTestUtils.sceneInteractor()
+ kosmos.sceneInteractor
}
private val keyguardStatusBarInteractor =
KeyguardStatusBarInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
index e6b9d9b..b6a033a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
@@ -40,13 +40,18 @@
import com.android.systemui.common.shared.model.Text
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
+import com.android.systemui.telephony.domain.interactor.telephonyInteractor
+import com.android.systemui.testKosmos
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.source.UserRecord
@@ -98,8 +103,8 @@
@Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var spyContext: Context
private lateinit var userRepository: FakeUserRepository
private lateinit var keyguardReply: KeyguardInteractorFactory.WithDependencies
@@ -121,16 +126,17 @@
SUPERVISED_USER_CREATION_APP_PACKAGE,
)
- utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
mSetFlagsRule.enableFlags(AConfigFlags.FLAG_SWITCH_USER_ON_BG)
spyContext = spy(context)
- keyguardReply = KeyguardInteractorFactory.create(featureFlags = utils.fakeFeatureFlags)
+ keyguardReply =
+ KeyguardInteractorFactory.create(featureFlags = kosmos.fakeFeatureFlagsClassic)
keyguardRepository = keyguardReply.repository
userRepository = FakeUserRepository()
refreshUsersScheduler =
RefreshUsersScheduler(
applicationScope = testScope.backgroundScope,
- mainDispatcher = utils.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
repository = userRepository,
)
}
@@ -363,7 +369,7 @@
fun actions_deviceUnlocked_fullScreen() {
createUserInteractor()
testScope.runTest {
- utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
@@ -447,7 +453,7 @@
fun actions_deviceLockedAddFromLockscreenSet_fullList_fullScreen() {
createUserInteractor()
testScope.runTest {
- utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
@@ -640,7 +646,7 @@
val refreshUsersCallCount = userRepository.refreshUsersCallCount
- utils.telephonyRepository.setCallState(1)
+ kosmos.fakeTelephonyRepository.setCallState(1)
runCurrent()
assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
@@ -792,7 +798,7 @@
fun userRecordsFullScreen() {
createUserInteractor()
testScope.runTest {
- utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
val userInfos = createUserInfos(count = 3, includeGuest = false)
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
userRepository.setUserInfos(userInfos)
@@ -901,7 +907,7 @@
fun showUserSwitcher_fullScreenEnabled_launchesFullScreenDialog() {
createUserInteractor()
testScope.runTest {
- utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
val expandable = mock<Expandable>()
underTest.showUserSwitcher(expandable)
@@ -1116,19 +1122,19 @@
manager = manager,
headlessSystemUserMode = headlessSystemUserMode,
applicationScope = testScope.backgroundScope,
- telephonyInteractor = utils.telephonyInteractor(),
+ telephonyInteractor = kosmos.telephonyInteractor,
broadcastDispatcher = fakeBroadcastDispatcher,
keyguardUpdateMonitor = keyguardUpdateMonitor,
- backgroundDispatcher = utils.testDispatcher,
- mainDispatcher = utils.testDispatcher,
+ backgroundDispatcher = kosmos.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
guestUserInteractor =
GuestUserInteractor(
applicationContext = spyContext,
applicationScope = testScope.backgroundScope,
- mainDispatcher = utils.testDispatcher,
- backgroundDispatcher = utils.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
+ backgroundDispatcher = kosmos.testDispatcher,
manager = manager,
repository = userRepository,
deviceProvisionedController = deviceProvisionedController,
@@ -1139,7 +1145,7 @@
resetOrExitSessionReceiver = resetOrExitSessionReceiver,
),
uiEventLogger = uiEventLogger,
- featureFlags = utils.fakeFeatureFlags,
+ featureFlags = kosmos.fakeFeatureFlagsClassic,
userRestrictionChecker = mock(),
)
}
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 8920d4d..1ed045f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -117,11 +117,11 @@
import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.scene.FakeWindowRootViewComponent;
-import com.android.systemui.scene.SceneTestUtils;
import com.android.systemui.scene.data.repository.SceneContainerRepository;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
@@ -356,8 +356,8 @@
@Mock
private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
- private final SceneTestUtils mUtils = new SceneTestUtils(this);
- private final TestScope mTestScope = mUtils.getTestScope();
+ private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+ private final TestScope mTestScope = mKosmos.getTestScope();
private ShadeInteractor mShadeInteractor;
private ShellTaskOrganizer mShellTaskOrganizer;
private TaskViewTransitions mTaskViewTransitions;
@@ -408,8 +408,8 @@
FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
PowerInteractor powerInteractor = new PowerInteractor(
- mUtils.getPowerRepository(),
- mUtils.falsingCollector(),
+ mKosmos.getPowerRepository(),
+ mKosmos.getFalsingCollector(),
mock(ScreenOffAnimationController.class),
mStatusBarStateController);
@@ -417,7 +417,7 @@
mTestScope.getBackgroundScope(),
new SceneContainerRepository(
mTestScope.getBackgroundScope(),
- mUtils.fakeSceneContainerConfig()),
+ mKosmos.getFakeSceneContainerConfig()),
powerInteractor,
mock(SceneLogger.class));
@@ -449,8 +449,8 @@
keyguardTransitionRepository,
keyguardTransitionInteractor,
mTestScope.getBackgroundScope(),
- mUtils.getTestDispatcher(),
- mUtils.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
keyguardInteractor,
featureFlags,
shadeRepository,
@@ -475,8 +475,8 @@
keyguardTransitionRepository,
keyguardTransitionInteractor,
mTestScope.getBackgroundScope(),
- mUtils.getTestDispatcher(),
- mUtils.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
keyguardInteractor,
featureFlags,
mock(KeyguardSecurityModel.class),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
index 42ec8fed..f192de2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
@@ -26,12 +26,24 @@
private val _isConfirmationRequired = MutableStateFlow(false)
override val isConfirmationRequired = _isConfirmationRequired.asStateFlow()
+ private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null)
+ override val opPackageName = _opPackageName.asStateFlow()
+
override fun setPrompt(
promptInfo: PromptInfo,
userId: Int,
gatekeeperChallenge: Long?,
kind: PromptKind,
- ) = setPrompt(promptInfo, userId, gatekeeperChallenge, kind, forceConfirmation = false)
+ opPackageName: String,
+ ) =
+ setPrompt(
+ promptInfo,
+ userId,
+ gatekeeperChallenge,
+ kind,
+ forceConfirmation = false,
+ opPackageName = opPackageName
+ )
fun setPrompt(
promptInfo: PromptInfo,
@@ -39,12 +51,14 @@
gatekeeperChallenge: Long?,
kind: PromptKind,
forceConfirmation: Boolean = false,
+ opPackageName: String? = null,
) {
_promptInfo.value = promptInfo
_userId.value = userId
_challenge.value = gatekeeperChallenge
_kind.value = kind
_isConfirmationRequired.value = promptInfo.isConfirmationRequested || forceConfirmation
+ _opPackageName.value = opPackageName
}
override fun unsetPrompt() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryKosmos.kt
new file mode 100644
index 0000000..79107cc
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.communal.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.communalPrefsRepository: CommunalPrefsRepository by
+ Kosmos.Fixture { fakeCommunalPrefsRepository }
+val Kosmos.fakeCommunalPrefsRepository by Kosmos.Fixture { FakeCommunalPrefsRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt
new file mode 100644
index 0000000..d3ed58b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.communal.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Fake implementation of [CommunalPrefsRepository] */
+class FakeCommunalPrefsRepository : CommunalPrefsRepository {
+ private val _isCtaDismissed = MutableStateFlow(false)
+ override val isCtaDismissed: Flow<Boolean> = _isCtaDismissed.asStateFlow()
+
+ override suspend fun setCtaDismissedForCurrentUser() {
+ _isCtaDismissed.value = true
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
index e82cae4..c85c27e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
@@ -9,7 +9,6 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
@@ -53,12 +52,4 @@
fun setIsCommunalHubShowing(isCommunalHubShowing: Boolean) {
_isCommunalHubShowing.value = isCommunalHubShowing
}
-
- private val _isCtaTileInViewModeVisible: MutableStateFlow<Boolean> = MutableStateFlow(true)
- override val isCtaTileInViewModeVisible: Flow<Boolean> =
- _isCtaTileInViewModeVisible.asStateFlow()
-
- override fun setCtaTileInViewModeVisibility(isVisible: Boolean) {
- _isCtaTileInViewModeVisible.value = isVisible
- }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
index 95ff889..126bd6f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
@@ -17,11 +17,12 @@
package com.android.systemui.communal.domain.interactor
-import android.appwidget.AppWidgetHost
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -42,7 +43,8 @@
mediaRepository: FakeCommunalMediaRepository = FakeCommunalMediaRepository(),
smartspaceRepository: FakeSmartspaceRepository = FakeSmartspaceRepository(),
tutorialRepository: FakeCommunalTutorialRepository = FakeCommunalTutorialRepository(),
- appWidgetHost: AppWidgetHost = mock(),
+ communalPrefsRepository: FakeCommunalPrefsRepository = FakeCommunalPrefsRepository(),
+ appWidgetHost: CommunalAppWidgetHost = mock(),
editWidgetsActivityStarter: EditWidgetsActivityStarter = mock(),
): WithDependencies {
val withDeps =
@@ -55,6 +57,7 @@
testScope,
communalRepository,
widgetRepository,
+ communalPrefsRepository,
mediaRepository,
smartspaceRepository,
tutorialRepository,
@@ -66,6 +69,7 @@
CommunalInteractor(
communalRepository,
widgetRepository,
+ communalPrefsRepository,
mediaRepository,
smartspaceRepository,
withDeps.keyguardInteractor,
@@ -79,13 +83,14 @@
val testScope: TestScope,
val communalRepository: FakeCommunalRepository,
val widgetRepository: FakeCommunalWidgetRepository,
+ val communalPrefsRepository: FakeCommunalPrefsRepository,
val mediaRepository: FakeCommunalMediaRepository,
val smartspaceRepository: FakeSmartspaceRepository,
val tutorialRepository: FakeCommunalTutorialRepository,
val keyguardRepository: FakeKeyguardRepository,
val keyguardInteractor: KeyguardInteractor,
val tutorialInteractor: CommunalTutorialInteractor,
- val appWidgetHost: AppWidgetHost,
+ val appWidgetHost: CommunalAppWidgetHost,
val editWidgetsActivityStarter: EditWidgetsActivityStarter,
val communalInteractor: CommunalInteractor,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 649b373..7cbbaab 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.communal.domain.interactor
import com.android.systemui.communal.data.repository.communalMediaRepository
+import com.android.systemui.communal.data.repository.communalPrefsRepository
import com.android.systemui.communal.data.repository.communalRepository
import com.android.systemui.communal.data.repository.communalWidgetRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -30,6 +31,7 @@
communalRepository = communalRepository,
widgetRepository = communalWidgetRepository,
mediaRepository = communalMediaRepository,
+ communalPrefsRepository = communalPrefsRepository,
smartspaceRepository = smartspaceRepository,
appWidgetHost = mock(),
keyguardInteractor = keyguardInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt
index 59f0ec3..ffca83b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt
@@ -16,11 +16,12 @@
package com.android.systemui.keyguard.domain.interactor
-import android.appwidget.AppWidgetHost
import com.android.systemui.communal.data.repository.communalMediaRepository
+import com.android.systemui.communal.data.repository.communalPrefsRepository
import com.android.systemui.communal.data.repository.communalRepository
import com.android.systemui.communal.data.repository.communalWidgetRepository
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.smartspace.data.repository.smartspaceRepository
@@ -32,9 +33,10 @@
communalRepository = communalRepository,
widgetRepository = communalWidgetRepository,
mediaRepository = communalMediaRepository,
+ communalPrefsRepository = communalPrefsRepository,
smartspaceRepository = smartspaceRepository,
keyguardInteractor = keyguardInteractor,
- appWidgetHost = mock(AppWidgetHost::class.java),
+ appWidgetHost = mock(CommunalAppWidgetHost::class.java),
editWidgetsActivityStarter = mock(EditWidgetsActivityStarter::class.java),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
new file mode 100644
index 0000000..24670b1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.kosmos
+
+import android.content.applicationContext
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.bouncerRepository
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
+import com.android.systemui.classifier.falsingCollector
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.jank.interactionJankMonitor
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.sceneContainerConfig
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.statusbar.phone.screenOffAnimationController
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.util.time.systemClock
+
+/** Helper for using [Kosmos] from Java. */
+@Deprecated("Please convert your test to Kotlin and use [Kosmos] directly.")
+class KosmosJavaAdapter(
+ testCase: SysuiTestCase,
+) {
+
+ private val kosmos = Kosmos()
+
+ val testDispatcher by lazy { kosmos.testDispatcher }
+ val testScope by lazy { kosmos.testScope }
+ val fakeFeatureFlags by lazy { kosmos.fakeFeatureFlagsClassic }
+ val fakeSceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags }
+ val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
+ val configurationInteractor by lazy { kosmos.configurationInteractor }
+ val bouncerRepository by lazy { kosmos.bouncerRepository }
+ val communalRepository by lazy { kosmos.fakeCommunalRepository }
+ val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+ val powerRepository by lazy { kosmos.fakePowerRepository }
+ val clock by lazy { kosmos.systemClock }
+ val mobileConnectionsRepository by lazy { kosmos.fakeMobileConnectionsRepository }
+ val simBouncerInteractor by lazy { kosmos.simBouncerInteractor }
+ val statusBarStateController by lazy { kosmos.statusBarStateController }
+ val interactionJankMonitor by lazy { kosmos.interactionJankMonitor }
+ val screenOffAnimationController by lazy { kosmos.screenOffAnimationController }
+ val fakeSceneContainerConfig by lazy { kosmos.sceneContainerConfig }
+ val sceneInteractor by lazy { kosmos.sceneInteractor }
+ val falsingCollector by lazy { kosmos.falsingCollector }
+ val powerInteractor by lazy { kosmos.powerInteractor }
+
+ init {
+ kosmos.applicationContext = testCase.context
+ kosmos.testCase = testCase
+ }
+}
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
deleted file mode 100644
index d314a25..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * 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.scene
-
-import android.content.Context
-import android.content.applicationContext
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
-import com.android.systemui.authentication.domain.interactor.authenticationInteractor
-import com.android.systemui.bouncer.data.repository.bouncerRepository
-import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository
-import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
-import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
-import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
-import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.classifier.domain.interactor.FalsingInteractor
-import com.android.systemui.classifier.domain.interactor.falsingInteractor
-import com.android.systemui.classifier.falsingCollector
-import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.configurationInteractor
-import com.android.systemui.communal.data.repository.fakeCommunalRepository
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.communalInteractor
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
-import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.jank.interactionJankMonitor
-import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testCase
-import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.plugins.statusbar.statusBarStateController
-import com.android.systemui.power.data.repository.fakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.power.domain.interactor.powerInteractor
-import com.android.systemui.scene.data.repository.SceneContainerRepository
-import com.android.systemui.scene.data.repository.sceneContainerRepository
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-import com.android.systemui.scene.shared.model.SceneContainerConfig
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
-import com.android.systemui.statusbar.phone.screenOffAnimationController
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
-import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
-import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
-import com.android.systemui.telephony.domain.interactor.telephonyInteractor
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.user.domain.interactor.selectedUserInteractor
-import com.android.systemui.util.time.systemClock
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-
-/**
- * Utilities for creating scene container framework related repositories, interactors, and
- * view-models for tests.
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-@Deprecated("Please use Kosmos instead.")
-class SceneTestUtils {
-
- val kosmos = Kosmos()
-
- constructor(
- context: Context,
- ) {
- kosmos.applicationContext = context
- }
-
- constructor(testCase: SysuiTestCase) : this(context = testCase.context) {
- kosmos.testCase = testCase
- }
-
- val testDispatcher by lazy { kosmos.testDispatcher }
- val testScope by lazy { kosmos.testScope }
- val fakeFeatureFlags by lazy { kosmos.fakeFeatureFlagsClassic }
- val fakeSceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags }
- val deviceEntryRepository by lazy { kosmos.fakeDeviceEntryRepository }
- val authenticationRepository by lazy { kosmos.fakeAuthenticationRepository }
- val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
- val configurationInteractor by lazy { kosmos.configurationInteractor }
- val telephonyRepository by lazy { kosmos.fakeTelephonyRepository }
- val bouncerRepository by lazy { kosmos.bouncerRepository }
- val communalRepository by lazy { kosmos.fakeCommunalRepository }
- val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
- val powerRepository by lazy { kosmos.fakePowerRepository }
- val simBouncerRepository by lazy { kosmos.fakeSimBouncerRepository }
- val clock by lazy { kosmos.systemClock }
- val mobileConnectionsRepository by lazy { kosmos.fakeMobileConnectionsRepository }
- val simBouncerInteractor by lazy { kosmos.simBouncerInteractor }
- val statusBarStateController by lazy { kosmos.statusBarStateController }
- val interactionJankMonitor by lazy { kosmos.interactionJankMonitor }
- val screenOffAnimationController by lazy { kosmos.screenOffAnimationController }
-
- fun fakeSceneContainerRepository(): SceneContainerRepository {
- return kosmos.sceneContainerRepository
- }
-
- fun fakeSceneKeys(): List<SceneKey> {
- return kosmos.sceneKeys
- }
-
- fun fakeSceneContainerConfig(): SceneContainerConfig {
- return kosmos.sceneContainerConfig
- }
-
- fun sceneInteractor(): SceneInteractor {
- return kosmos.sceneInteractor
- }
-
- fun deviceEntryInteractor(): DeviceEntryInteractor {
- return kosmos.deviceEntryInteractor
- }
-
- fun authenticationInteractor(): AuthenticationInteractor {
- return kosmos.authenticationInteractor
- }
-
- fun keyguardInteractor(): KeyguardInteractor {
- return kosmos.keyguardInteractor
- }
-
- fun communalInteractor(): CommunalInteractor {
- return kosmos.communalInteractor
- }
-
- fun bouncerInteractor(): BouncerInteractor {
- return kosmos.bouncerInteractor
- }
-
- fun notificationsPlaceholderViewModel(): NotificationsPlaceholderViewModel {
- return kosmos.notificationsPlaceholderViewModel
- }
-
- fun bouncerViewModel(): BouncerViewModel {
- return kosmos.bouncerViewModel
- }
-
- fun telephonyInteractor(): TelephonyInteractor {
- return kosmos.telephonyInteractor
- }
-
- fun falsingInteractor(): FalsingInteractor {
- return kosmos.falsingInteractor
- }
-
- fun falsingCollector(): FalsingCollector {
- return kosmos.falsingCollector
- }
-
- fun powerInteractor(): PowerInteractor {
- return kosmos.powerInteractor
- }
-
- fun selectedUserInteractor(): SelectedUserInteractor {
- return kosmos.selectedUserInteractor
- }
-
- fun bouncerActionButtonInteractor(): BouncerActionButtonInteractor {
- return kosmos.bouncerActionButtonInteractor
- }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
index 0dbade7..d7e948e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
@@ -20,11 +20,13 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.scene.shared.flag.sceneContainerFlags
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
val Kosmos.notificationsPlaceholderViewModel by Fixture {
NotificationsPlaceholderViewModel(
interactor = notificationStackAppearanceInteractor,
+ shadeInteractor = shadeInteractor,
flags = sceneContainerFlags,
featureFlags = featureFlagsClassic,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
index d9beabb..d80ee75 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
@@ -23,6 +23,7 @@
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.data.repository.windowRootViewVisibilityRepository
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.policy.headsUpManager
val Kosmos.windowRootViewVisibilityInteractor by Fixture {
@@ -32,5 +33,6 @@
keyguardRepository = keyguardRepository,
headsUpManager = headsUpManager,
powerInteractor = powerInteractor,
+ activeNotificationsInteractor = activeNotificationsInteractor,
)
}
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 5a44ac8..9d9e7c9 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -1387,6 +1387,8 @@
case BatteryManager.BATTERY_PROPERTY_MANUFACTURING_DATE:
case BatteryManager.BATTERY_PROPERTY_FIRST_USAGE_DATE:
case BatteryManager.BATTERY_PROPERTY_CHARGING_POLICY:
+ case BatteryManager.BATTERY_PROPERTY_SERIAL_NUMBER:
+ case BatteryManager.BATTERY_PROPERTY_PART_STATUS:
mContext.enforceCallingPermission(
android.Manifest.permission.BATTERY_STATS, null);
break;
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 2b35231..ea1b0f5 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1076,6 +1076,7 @@
final UserManager userManager = mContext.getSystemService(UserManager.class);
final List<UserInfo> users = userManager.getUsers();
+ extendWatchdogTimeout("#onReset might be slow");
mStorageSessionController.onReset(mVold, () -> {
mHandler.removeCallbacksAndMessages(null);
});
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index f921b0b..bd67cf420 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1126,6 +1126,21 @@
return;
}
+ int phoneId = -1;
+ int subscriptionId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+ if(Flags.preventSystemServerAndPhoneDeadlock()) {
+ // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
+ // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ if (DBG) {
+ log("invalid subscription id, use default id");
+ }
+ } else { //APP specify subID
+ subscriptionId = subId;
+ }
+ phoneId = getPhoneIdFromSubId(subscriptionId);
+ }
+
synchronized (mRecords) {
// register
IBinder b = callback.asBinder();
@@ -1145,17 +1160,23 @@
r.renounceFineLocationAccess = renounceFineLocationAccess;
r.callerUid = Binder.getCallingUid();
r.callerPid = Binder.getCallingPid();
- // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
- // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
- if (!SubscriptionManager.isValidSubscriptionId(subId)) {
- if (DBG) {
- log("invalid subscription id, use default id");
+
+ if(!Flags.preventSystemServerAndPhoneDeadlock()) {
+ // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
+ // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ if (DBG) {
+ log("invalid subscription id, use default id");
+ }
+ r.subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+ } else {//APP specify subID
+ r.subId = subId;
}
- r.subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
- } else {//APP specify subID
- r.subId = subId;
+ r.phoneId = getPhoneIdFromSubId(r.subId);
+ } else {
+ r.subId = subscriptionId;
+ r.phoneId = phoneId;
}
- r.phoneId = getPhoneIdFromSubId(r.subId);
r.eventList = events;
if (DBG) {
@@ -1893,8 +1914,14 @@
}
private void notifyCarrierNetworkChangeWithPermission(int subId, boolean active) {
+ int phoneId = -1;
+ if(Flags.preventSystemServerAndPhoneDeadlock()) {
+ phoneId = getPhoneIdFromSubId(subId);
+ }
synchronized (mRecords) {
- int phoneId = getPhoneIdFromSubId(subId);
+ if(!Flags.preventSystemServerAndPhoneDeadlock()) {
+ phoneId = getPhoneIdFromSubId(subId);
+ }
mCarrierNetworkChangeState[phoneId] = active;
if (VDBG) {
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 66a10e4..cd8be33 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -16,8 +16,10 @@
package com.android.server;
+import static android.app.Flags.modesApi;
import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE;
import static android.app.UiModeManager.DEFAULT_PRIORITY;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
import static android.app.UiModeManager.MODE_NIGHT_AUTO;
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
@@ -50,6 +52,7 @@
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.app.UiModeManager;
+import android.app.UiModeManager.AttentionModeThemeOverlayType;
import android.app.UiModeManager.NightModeCustomReturnType;
import android.app.UiModeManager.NightModeCustomType;
import android.content.BroadcastReceiver;
@@ -134,6 +137,7 @@
private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
private int mNightMode = UiModeManager.MODE_NIGHT_NO;
private int mNightModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
+ private int mAttentionModeThemeOverlay = UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
private final LocalTime DEFAULT_CUSTOM_NIGHT_START_TIME = LocalTime.of(22, 0);
private final LocalTime DEFAULT_CUSTOM_NIGHT_END_TIME = LocalTime.of(6, 0);
private LocalTime mCustomAutoNightModeStartMilliseconds = DEFAULT_CUSTOM_NIGHT_START_TIME;
@@ -839,6 +843,8 @@
? customModeType
: MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
mNightMode = mode;
+ //deactivates AttentionMode if user toggles DarkTheme
+ mAttentionModeThemeOverlay = MODE_ATTENTION_THEME_OVERLAY_OFF;
resetNightModeOverrideLocked();
persistNightMode(user);
// on screen off will update configuration instead
@@ -879,6 +885,29 @@
}
}
+ @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
+ @Override
+ public void setAttentionModeThemeOverlay(
+ @AttentionModeThemeOverlayType int attentionModeThemeOverlayType) {
+ setAttentionModeThemeOverlay_enforcePermission();
+
+ synchronized (mLock) {
+ if (mAttentionModeThemeOverlay != attentionModeThemeOverlayType) {
+ mAttentionModeThemeOverlay = attentionModeThemeOverlayType;
+ Binder.withCleanCallingIdentity(()-> updateLocked(0, 0));
+ }
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
+ @Override
+ public @AttentionModeThemeOverlayType int getAttentionModeThemeOverlay() {
+ getAttentionModeThemeOverlay_enforcePermission();
+ synchronized (mLock) {
+ return mAttentionModeThemeOverlay;
+ }
+ }
+
@Override
public void setApplicationNightMode(@UiModeManager.NightMode int mode) {
switch (mode) {
@@ -1406,7 +1435,7 @@
pw.print(Shell.nightModeToStr(mNightMode, mNightModeCustomType)); pw.print(") ");
pw.print(" mOverrideOn/Off="); pw.print(mOverrideNightModeOn);
pw.print("/"); pw.print(mOverrideNightModeOff);
-
+ pw.print(" mAttentionModeThemeOverlay="); pw.print(mAttentionModeThemeOverlay);
pw.print(" mNightModeLocked="); pw.println(mNightModeLocked);
pw.print(" mCarModeEnabled="); pw.print(mCarModeEnabled);
@@ -1685,7 +1714,7 @@
}
@UiModeManager.NightMode
- private int getComputedUiModeConfiguration(@UiModeManager.NightMode int uiMode) {
+ private int getComputedUiModeConfiguration(int uiMode) {
uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
: Configuration.UI_MODE_NIGHT_NO;
uiMode &= mComputedNightMode ? ~Configuration.UI_MODE_NIGHT_NO
@@ -1980,18 +2009,26 @@
}
private void updateComputedNightModeLocked(boolean activate) {
- mComputedNightMode = activate;
- if (mNightMode == MODE_NIGHT_YES || mNightMode == UiModeManager.MODE_NIGHT_NO) {
- return;
+ boolean newComputedValue = activate;
+ if (mNightMode != MODE_NIGHT_YES && mNightMode != UiModeManager.MODE_NIGHT_NO) {
+ if (mOverrideNightModeOn && !newComputedValue) {
+ newComputedValue = true;
+ } else if (mOverrideNightModeOff && newComputedValue) {
+ newComputedValue = false;
+ }
}
- if (mOverrideNightModeOn && !mComputedNightMode) {
- mComputedNightMode = true;
- return;
+
+ if (modesApi()) {
+ // Computes final night mode values based on Attention Mode.
+ mComputedNightMode = switch (mAttentionModeThemeOverlay) {
+ case (UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT) -> true;
+ case (UiModeManager.MODE_ATTENTION_THEME_OVERLAY_DAY) -> false;
+ default -> newComputedValue; // case OFF
+ };
+ } else {
+ mComputedNightMode = newComputedValue;
}
- if (mOverrideNightModeOff && mComputedNightMode) {
- mComputedNightMode = false;
- return;
- }
+
if (mNightMode != MODE_NIGHT_AUTO || (mTwilightManager != null
&& mTwilightManager.getLastTwilightState() != null)) {
resetNightModeOverrideLocked();
diff --git a/services/core/java/com/android/server/adaptiveauth/OWNERS b/services/core/java/com/android/server/adaptiveauth/OWNERS
new file mode 100644
index 0000000..b188105
--- /dev/null
+++ b/services/core/java/com/android/server/adaptiveauth/OWNERS
@@ -0,0 +1 @@
+hainingc@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index df8d9e1..8fa0089 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -65,6 +65,9 @@
import static android.app.AppOpsManager.opToName;
import static android.app.AppOpsManager.opToPublicName;
import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
+import static android.content.Intent.ACTION_PACKAGE_ADDED;
+import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+import static android.content.Intent.EXTRA_REPLACING;
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
@@ -973,7 +976,29 @@
String pkgName = intent.getData().getEncodedSchemeSpecificPart();
int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
- if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
+ if (action.equals(ACTION_PACKAGE_ADDED)
+ && !intent.getBooleanExtra(EXTRA_REPLACING, false)) {
+ PackageInfo pi = getPackageManagerInternal().getPackageInfo(pkgName,
+ PackageManager.GET_PERMISSIONS, Process.myUid(),
+ UserHandle.getUserId(uid));
+ boolean isSamplingTarget = isSamplingTarget(pi);
+ synchronized (AppOpsService.this) {
+ if (isSamplingTarget) {
+ mRarelyUsedPackages.add(pkgName);
+ }
+ UidState uidState = getUidStateLocked(uid, true);
+ if (!uidState.pkgOps.containsKey(pkgName)) {
+ uidState.pkgOps.put(pkgName,
+ new Ops(pkgName, uidState));
+ }
+
+ createSandboxUidStateIfNotExistsForAppLocked(uid);
+ }
+ } else if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) {
+ synchronized (AppOpsService.this) {
+ packageRemovedLocked(uid, pkgName);
+ }
+ } else if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
AndroidPackage pkg = getPackageManagerInternal().getPackage(pkgName);
if (pkg == null) {
return;
@@ -1052,7 +1077,9 @@
mHistoricalRegistry.systemReady(mContext.getContentResolver());
IntentFilter packageUpdateFilter = new IntentFilter();
+ packageUpdateFilter.addAction(ACTION_PACKAGE_ADDED);
packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ packageUpdateFilter.addAction(ACTION_PACKAGE_REMOVED);
packageUpdateFilter.addDataScheme("package");
mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL,
@@ -1079,7 +1106,7 @@
String action;
if (!ArrayUtils.contains(pkgsInUid, pkg)) {
- action = Intent.ACTION_PACKAGE_REMOVED;
+ action = ACTION_PACKAGE_REMOVED;
} else {
action = Intent.ACTION_PACKAGE_REPLACED;
}
@@ -1160,44 +1187,6 @@
// onUserRemoved handled by #removeUser
});
-
- getPackageManagerInternal().getPackageList(
- new PackageManagerInternal.PackageListObserver() {
- @Override
- public void onPackageAdded(String packageName, int appId) {
- PackageInfo pi = getPackageManagerInternal().getPackageInfo(packageName,
- PackageManager.GET_PERMISSIONS, Process.myUid(),
- mContext.getUserId());
- boolean isSamplingTarget = isSamplingTarget(pi);
- int[] userIds = getUserManagerInternal().getUserIds();
- synchronized (AppOpsService.this) {
- if (isSamplingTarget) {
- mRarelyUsedPackages.add(packageName);
- }
- for (int i = 0; i < userIds.length; i++) {
- int uid = UserHandle.getUid(userIds[i], appId);
- UidState uidState = getUidStateLocked(uid, true);
- if (!uidState.pkgOps.containsKey(packageName)) {
- uidState.pkgOps.put(packageName,
- new Ops(packageName, uidState));
- }
-
- createSandboxUidStateIfNotExistsForAppLocked(uid);
- }
- }
- }
-
- @Override
- public void onPackageRemoved(String packageName, int appId) {
- int[] userIds = getUserManagerInternal().getUserIds();
- synchronized (AppOpsService.this) {
- for (int i = 0; i < userIds.length; i++) {
- int uid = UserHandle.getUid(userIds[i], appId);
- packageRemovedLocked(uid, packageName);
- }
- }
- }
- });
}
/**
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index d5d8fd2..8fd2ee2 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -20,6 +20,7 @@
// TODO(b/141025588): Create separate internal and external permissions for AuthService.
// TODO(b/141025588): Get rid of the USE_FINGERPRINT permission.
+import static android.Manifest.permission.MANAGE_BIOMETRIC_DIALOG;
import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
@@ -304,6 +305,9 @@
if (promptInfo.containsPrivateApiConfigurations()) {
checkInternalPermission();
}
+ if (promptInfo.containsManageBioApiConfigurations()) {
+ checkManageBiometricPermission();
+ }
final long identity = Binder.clearCallingIdentity();
try {
@@ -984,6 +988,11 @@
"Must have USE_BIOMETRIC_INTERNAL permission");
}
+ private void checkManageBiometricPermission() {
+ getContext().enforceCallingOrSelfPermission(MANAGE_BIOMETRIC_DIALOG,
+ "Must have MANAGE_BIOMETRIC_DIALOG permission");
+ }
+
private void checkPermission() {
if (getContext().checkCallingOrSelfPermission(USE_FINGERPRINT)
!= PackageManager.PERMISSION_GRANTED) {
diff --git a/services/core/java/com/android/server/broadcastradio/TEST_MAPPING b/services/core/java/com/android/server/broadcastradio/TEST_MAPPING
new file mode 100644
index 0000000..ee4eeb6
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "imports": [
+ {
+ "path": "frameworks/base/core/tests/BroadcastRadioTests"
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java b/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
index 1153cc3..8a3a56c 100644
--- a/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
+++ b/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
@@ -16,6 +16,8 @@
package com.android.server.health;
+import static android.os.Flags.batteryPartStatusApi;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.health.BatteryHealthData;
@@ -150,6 +152,18 @@
healthData = service.getBatteryHealthData();
prop.setLong(healthData.batteryStateOfHealth);
break;
+ case BatteryManager.BATTERY_PROPERTY_SERIAL_NUMBER:
+ if (batteryPartStatusApi()) {
+ healthData = service.getBatteryHealthData();
+ prop.setString(healthData.batterySerialNumber);
+ }
+ break;
+ case BatteryManager.BATTERY_PROPERTY_PART_STATUS:
+ if (batteryPartStatusApi()) {
+ healthData = service.getBatteryHealthData();
+ prop.setLong(healthData.batteryPartStatus);
+ }
+ break;
}
} catch (UnsupportedOperationException e) {
// Leave prop untouched.
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index 4d19ead..d7188c7 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -42,7 +42,6 @@
import android.util.Log;
import android.util.Slog;
-import com.android.internal.annotations.Keep;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.RingBuffer;
import com.android.server.am.ProcessList;
@@ -414,7 +413,7 @@
private static final Date sDate = new Date();
public LogBuffer(int capacity) {
- super(Data.class, capacity);
+ super(Data::new, Data[]::new, capacity);
}
public void uidStateChanged(int uid, int procState, long procStateSeq,
@@ -690,12 +689,8 @@
/**
* Container class for all networkpolicy events data.
- *
- * Note: This class needs to be public for RingBuffer class to be able to create
- * new instances of this.
*/
- @Keep
- public static final class Data {
+ private static final class Data {
public int type;
public long timeStamp;
diff --git a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
index 71a6b5e..ab650af 100644
--- a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
+++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
@@ -16,7 +16,8 @@
package com.android.server.notification;
-import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
import android.app.UiModeManager;
import android.app.WallpaperManager;
@@ -128,10 +129,9 @@
private void updateNightModeImmediately(boolean useNightMode) {
Binder.withCleanCallingIdentity(() -> {
- // TODO: b/314285749 - Placeholder; use real APIs when available.
- mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
- mUiModeManager.setNightModeActivatedForCustomMode(MODE_NIGHT_CUSTOM_TYPE_BEDTIME,
- useNightMode);
+ mUiModeManager.setAttentionModeThemeOverlay(
+ useNightMode ? MODE_ATTENTION_THEME_OVERLAY_NIGHT
+ : MODE_ATTENTION_THEME_OVERLAY_OFF);
});
}
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 3e3b72c..3659cb7 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -485,8 +485,6 @@
synchronized (mPm.mInstallLock) {
cleanUpResourcesLI(codeFile, instructionSets);
}
- // TODO: open logging to help debug, will delete or add debug flag
- Slog.d(TAG, "cleanUpResources for " + codeFile);
if (packageName == null) {
return;
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index e993d9e..d644235 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -33,6 +33,7 @@
import android.app.appsearch.SearchResults;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaRequest;
+import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
@@ -47,6 +48,7 @@
import android.os.Binder;
import android.os.PersistableBundle;
import android.os.StrictMode;
+import android.os.SystemClock;
import android.text.format.Formatter;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -192,6 +194,9 @@
private long mLastKnownForegroundElapsedTime;
@GuardedBy("mLock")
+ private long mLastReportedTime;
+
+ @GuardedBy("mLock")
private boolean mIsAppSearchSchemaUpToDate;
private ShortcutPackage(ShortcutUser shortcutUser,
@@ -1673,6 +1678,26 @@
return condition[0];
}
+ void reportShortcutUsed(@NonNull final UsageStatsManagerInternal usageStatsManagerInternal,
+ @NonNull final String shortcutId) {
+ synchronized (mLock) {
+ final long currentTS = SystemClock.elapsedRealtime();
+ final ShortcutService s = mShortcutUser.mService;
+ if (currentTS - mLastReportedTime > s.mSaveDelayMillis) {
+ mLastReportedTime = currentTS;
+ } else {
+ return;
+ }
+ final long token = s.injectClearCallingIdentity();
+ try {
+ usageStatsManagerInternal.reportShortcutUsage(getPackageName(), shortcutId,
+ getPackageUserId());
+ } finally {
+ s.injectRestoreCallingIdentity(token);
+ }
+ }
+ }
+
public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
pw.println();
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 96c205c..c23d2ab 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -371,7 +371,7 @@
private CompressFormat mIconPersistFormat;
private int mIconPersistQuality;
- private int mSaveDelayMillis;
+ int mSaveDelayMillis;
private final IPackageManager mIPackageManager;
private final PackageManagerInternal mPackageManagerInternal;
@@ -2291,7 +2291,7 @@
packageShortcutsChanged(ps, changedShortcuts, removedShortcuts);
- reportShortcutUsedInternal(packageName, shortcut.getId(), userId);
+ ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcut.getId());
verifyStates();
}
@@ -2695,25 +2695,17 @@
Slog.d(TAG, String.format("reportShortcutUsed: Shortcut %s package %s used on user %d",
shortcutId, packageName, userId));
}
+ final ShortcutPackage ps;
synchronized (mLock) {
throwIfUserLockedL(userId);
- final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
+ ps = getPackageShortcutsForPublisherLocked(packageName, userId);
if (ps.findShortcutById(shortcutId) == null) {
Log.w(TAG, String.format("reportShortcutUsed: package %s doesn't have shortcut %s",
packageName, shortcutId));
return;
}
}
- reportShortcutUsedInternal(packageName, shortcutId, userId);
- }
-
- private void reportShortcutUsedInternal(String packageName, String shortcutId, int userId) {
- final long token = injectClearCallingIdentity();
- try {
- mUsageStatsManagerInternal.reportShortcutUsage(packageName, shortcutId, userId);
- } finally {
- injectRestoreCallingIdentity(token);
- }
+ ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcutId);
}
@Override
@@ -5202,13 +5194,11 @@
}
// Injection point.
- @VisibleForTesting
long injectClearCallingIdentity() {
return Binder.clearCallingIdentity();
}
// Injection point.
- @VisibleForTesting
void injectRestoreCallingIdentity(long token) {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7b0a69b..948e9cd 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -16,6 +16,8 @@
package com.android.server.pm;
+import static android.content.Intent.ACTION_SCREEN_OFF;
+import static android.content.Intent.ACTION_SCREEN_ON;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY;
@@ -41,6 +43,7 @@
import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.StringRes;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -74,8 +77,10 @@
import android.content.pm.parsing.FrameworkParsingPackageUtils;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.multiuser.Flags;
+import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -83,6 +88,7 @@
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.IProgressListener;
import android.os.IUserManager;
@@ -91,6 +97,7 @@
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.PersistableBundle;
+import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -296,6 +303,12 @@
private static final long BOOT_USER_SET_TIMEOUT_MS = 300_000;
+ /**
+ * The time duration (in milliseconds) post device inactivity after which the private space
+ * should be auto-locked if the corresponding settings option is selected by the user.
+ */
+ private static final long PRIVATE_SPACE_AUTO_LOCK_INACTIVITY_TIMEOUT_MS = 5 * 60 * 1000;
+
// Tron counters
private static final String TRON_GUEST_CREATED = "users_guest_created";
private static final String TRON_USER_CREATED = "users_user_created";
@@ -522,6 +535,36 @@
private final LockPatternUtils mLockPatternUtils;
+ private KeyguardManager.KeyguardLockedStateListener mKeyguardLockedStateListener;
+
+ /** Token to identify and remove already scheduled private space auto-lock messages */
+ private static final Object PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN = new Object();
+
+ /** Content observer to get callbacks for privte space autolock settings changes */
+ private final SettingsObserver mPrivateSpaceAutoLockSettingsObserver;
+
+ private final class SettingsObserver extends ContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (isAutoLockForPrivateSpaceEnabled()) {
+ final String path = uri.getLastPathSegment();
+ if (TextUtils.equals(path, Settings.Secure.PRIVATE_SPACE_AUTO_LOCK)) {
+ int autoLockPreference =
+ Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER,
+ getMainUserIdUnchecked());
+ Slog.i(LOG_TAG, "Auto-lock settings changed to " + autoLockPreference);
+ setOrUpdateAutoLockPreferenceForPrivateProfile(autoLockPreference);
+ }
+ }
+ }
+ }
+
private final String ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK =
"com.android.server.pm.DISABLE_QUIET_MODE_AFTER_UNLOCK";
@@ -534,12 +577,159 @@
final IntentSender target = intent.getParcelableExtra(Intent.EXTRA_INTENT, android.content.IntentSender.class);
final int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL);
final String callingPackage = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
- // Call setQuietModeEnabled on bg thread to avoid ANR
- BackgroundThread.getHandler().post(() ->
- setQuietModeEnabled(userId, false, target, callingPackage));
+ setQuietModeEnabledAsync(userId, false, target, callingPackage);
}
};
+ /** Checks if the device inactivity broadcast receiver is already registered*/
+ private boolean mIsDeviceInactivityBroadcastReceiverRegistered = false;
+
+ private final BroadcastReceiver mDeviceInactivityBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (isAutoLockForPrivateSpaceEnabled()) {
+ if (ACTION_SCREEN_OFF.equals(intent.getAction())) {
+ maybeScheduleMessageToAutoLockPrivateSpace();
+ } else if (ACTION_SCREEN_ON.equals(intent.getAction())) {
+ // Remove any queued messages since the device is interactive again
+ mHandler.removeCallbacksAndMessages(PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN);
+ }
+ }
+ }
+ };
+
+ @VisibleForTesting
+ void maybeScheduleMessageToAutoLockPrivateSpace() {
+ // No action needed if auto-lock on inactivity not selected
+ int privateSpaceAutoLockPreference =
+ Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER,
+ getMainUserIdUnchecked());
+ if (privateSpaceAutoLockPreference
+ != Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY) {
+ return;
+ }
+ int privateProfileUserId = getPrivateProfileUserId();
+ if (privateProfileUserId != UserHandle.USER_NULL) {
+ scheduleMessageToAutoLockPrivateSpace(privateProfileUserId,
+ PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN,
+ PRIVATE_SPACE_AUTO_LOCK_INACTIVITY_TIMEOUT_MS);
+ }
+ }
+
+ @VisibleForTesting
+ void scheduleMessageToAutoLockPrivateSpace(int userId, Object token,
+ long delayInMillis) {
+ mHandler.postDelayed(() -> {
+ final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
+ if (powerManager != null && !powerManager.isInteractive()) {
+ Slog.i(LOG_TAG, "Auto-locking private space with user-id " + userId);
+ setQuietModeEnabledAsync(userId, true,
+ /* target */ null, mContext.getPackageName());
+ } else {
+ Slog.i(LOG_TAG, "Device is interactive, skipping auto-lock");
+ }
+ }, token, delayInMillis);
+ }
+
+ @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)
+ private void initializeAndRegisterKeyguardLockedStateListener() {
+ mKeyguardLockedStateListener = this::tryAutoLockingPrivateSpaceOnKeyguardChanged;
+ // Register with keyguard to send locked state events to the listener initialized above
+ try {
+ final KeyguardManager keyguardManager =
+ mContext.getSystemService(KeyguardManager.class);
+ Slog.i(LOG_TAG, "Adding keyguard locked state listener");
+ keyguardManager.addKeyguardLockedStateListener(new HandlerExecutor(mHandler),
+ mKeyguardLockedStateListener);
+ } catch (Exception e) {
+ Slog.e(LOG_TAG, "Error adding keyguard locked listener ", e);
+ }
+ }
+
+ @VisibleForTesting
+ @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)
+ void setOrUpdateAutoLockPreferenceForPrivateProfile(
+ @Settings.Secure.PrivateSpaceAutoLockOption int autoLockPreference) {
+ int privateProfileUserId = getPrivateProfileUserId();
+ if (privateProfileUserId == UserHandle.USER_NULL) {
+ Slog.e(LOG_TAG, "Auto-lock preference updated but private space user not found");
+ return;
+ }
+
+ if (autoLockPreference == Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY) {
+ // Register inactivity broadcast
+ if (!mIsDeviceInactivityBroadcastReceiverRegistered) {
+ Slog.i(LOG_TAG, "Registering device inactivity broadcast receivers");
+ mContext.registerReceiver(mDeviceInactivityBroadcastReceiver,
+ new IntentFilter(ACTION_SCREEN_OFF),
+ null, mHandler);
+
+ mContext.registerReceiver(mDeviceInactivityBroadcastReceiver,
+ new IntentFilter(ACTION_SCREEN_ON),
+ null, mHandler);
+
+ mIsDeviceInactivityBroadcastReceiverRegistered = true;
+ }
+ } else {
+ // Unregister device inactivity broadcasts
+ if (mIsDeviceInactivityBroadcastReceiverRegistered) {
+ Slog.i(LOG_TAG, "Removing device inactivity broadcast receivers");
+ mHandler.removeCallbacksAndMessages(PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN);
+ mContext.unregisterReceiver(mDeviceInactivityBroadcastReceiver);
+ mIsDeviceInactivityBroadcastReceiverRegistered = false;
+ }
+ }
+
+ if (autoLockPreference == Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK) {
+ // Initialize and add keyguard state listener
+ initializeAndRegisterKeyguardLockedStateListener();
+ } else {
+ // Remove keyguard state listener
+ try {
+ final KeyguardManager keyguardManager =
+ mContext.getSystemService(KeyguardManager.class);
+ Slog.i(LOG_TAG, "Removing keyguard locked state listener");
+ keyguardManager.removeKeyguardLockedStateListener(mKeyguardLockedStateListener);
+ } catch (Exception e) {
+ Slog.e(LOG_TAG, "Error adding keyguard locked state listener ", e);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void tryAutoLockingPrivateSpaceOnKeyguardChanged(boolean isKeyguardLocked) {
+ if (isAutoLockForPrivateSpaceEnabled()) {
+ int autoLockPreference = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER,
+ getMainUserIdUnchecked());
+ boolean isAutoLockOnDeviceLockSelected =
+ autoLockPreference == Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK;
+ if (isKeyguardLocked && isAutoLockOnDeviceLockSelected) {
+ int privateProfileUserId = getPrivateProfileUserId();
+ if (privateProfileUserId != UserHandle.USER_NULL) {
+ Slog.i(LOG_TAG, "Auto-locking private space with user-id "
+ + privateProfileUserId);
+ setQuietModeEnabledAsync(privateProfileUserId,
+ /* enableQuietMode */true, /* target */ null,
+ mContext.getPackageName());
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void setQuietModeEnabledAsync(@UserIdInt int userId, boolean enableQuietMode,
+ IntentSender target, @Nullable String callingPackage) {
+ // Call setQuietModeEnabled on bg thread to avoid ANR
+ BackgroundThread.getHandler().post(
+ () -> setQuietModeEnabled(userId, enableQuietMode, target,
+ callingPackage)
+ );
+ }
+
/**
* Cache the owner name string, since it could be read repeatedly on a critical code path
* but hit by slow IO. This could be eliminated once we have the cached UserInfo in place.
@@ -790,9 +980,15 @@
mLockPatternUtils = new LockPatternUtils(mContext);
mUserStates.put(UserHandle.USER_SYSTEM, UserState.STATE_BOOTING);
mUser0Allocations = DBG_ALLOCATION ? new AtomicInteger() : null;
+ mPrivateSpaceAutoLockSettingsObserver = new SettingsObserver(mHandler);
emulateSystemUserModeIfNeeded();
}
+ private static boolean isAutoLockForPrivateSpaceEnabled() {
+ return android.os.Flags.allowPrivateProfile()
+ && Flags.supportAutolockForPrivateSpace();
+ }
+
void systemReady() {
mAppOpsService = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
@@ -809,6 +1005,20 @@
new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED),
null, mHandler);
+ if (isAutoLockForPrivateSpaceEnabled()) {
+
+ int mainUserId = getMainUserIdUnchecked();
+
+ mContext.getContentResolver().registerContentObserverAsUser(Settings.Secure.getUriFor(
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK), false,
+ mPrivateSpaceAutoLockSettingsObserver, UserHandle.of(mainUserId));
+
+ setOrUpdateAutoLockPreferenceForPrivateProfile(
+ Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER, mainUserId));
+ }
+
markEphemeralUsersForRemoval();
}
@@ -973,6 +1183,18 @@
return UserHandle.USER_NULL;
}
+ private @UserIdInt int getPrivateProfileUserId() {
+ synchronized (mUsersLock) {
+ for (int userId : getUserIds()) {
+ UserInfo userInfo = getUserInfoLU(userId);
+ if (userInfo != null && userInfo.isPrivateProfile()) {
+ return userInfo.id;
+ }
+ }
+ }
+ return UserHandle.USER_NULL;
+ }
+
@Override
public void setBootUser(@UserIdInt int userId) {
checkCreateUsersPermission("Set boot user");
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index 60dc4ff..d95b431 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -348,6 +348,10 @@
private void pinWebviewIfRequired(ApplicationInfo appInfo) {
PinnerService pinnerService = LocalServices.getService(PinnerService.class);
+ if (pinnerService == null) {
+ // This happens in unit tests which do not have services.
+ return;
+ }
int webviewPinQuota = pinnerService.getWebviewPinQuota();
if (webviewPinQuota <= 0) {
return;
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index e7621ff..182e1c1 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -18,6 +18,7 @@
import static android.app.ActivityManager.INTENT_SENDER_ACTIVITY;
import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
@@ -144,22 +145,20 @@
}
private IntentSender createIntentSenderForOriginalIntent(int callingUid, int flags) {
- Bundle bOptions = deferCrossProfileAppsAnimationIfNecessary();
+ ActivityOptions activityOptions = deferCrossProfileAppsAnimationIfNecessary();
+ activityOptions.setPendingIntentCreatorBackgroundActivityStartMode(
+ MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
final TaskFragment taskFragment = getLaunchTaskFragment();
// If the original intent is going to be embedded, try to forward the embedding TaskFragment
// and its task id to embed back the original intent.
if (taskFragment != null) {
- ActivityOptions activityOptions = bOptions != null
- ? ActivityOptions.fromBundle(bOptions)
- : ActivityOptions.makeBasic();
activityOptions.setLaunchTaskFragmentToken(taskFragment.getFragmentToken());
- bOptions = activityOptions.toBundle();
}
final IIntentSender target = mService.getIntentSenderLocked(
INTENT_SENDER_ACTIVITY, mCallingPackage, mCallingFeatureId, callingUid, mUserId,
null /*token*/, null /*resultCode*/, 0 /*requestCode*/,
new Intent[] { mIntent }, new String[] { mResolvedType },
- flags, bOptions);
+ flags, activityOptions.toBundle());
return new IntentSender(target);
}
@@ -272,12 +271,12 @@
*
* @return the activity option used to start the original intent.
*/
- private Bundle deferCrossProfileAppsAnimationIfNecessary() {
+ private ActivityOptions deferCrossProfileAppsAnimationIfNecessary() {
if (hasCrossProfileAnimation()) {
mActivityOptions = null;
- return ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle();
+ return ActivityOptions.makeOpenCrossProfileAppsAnimation();
}
- return null;
+ return ActivityOptions.makeBasic();
}
private boolean interceptQuietProfileIfNeeded() {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 314d720..8f9ed83 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3558,9 +3558,11 @@
&& top.mLetterboxUiController.isSystemOverrideToFullscreenEnabled();
appCompatTaskInfo.isFromLetterboxDoubleTap = top != null
&& top.mLetterboxUiController.isFromDoubleTap();
- if (appCompatTaskInfo.isLetterboxDoubleTapEnabled) {
+ if (top != null) {
appCompatTaskInfo.topActivityLetterboxWidth = top.getBounds().width();
appCompatTaskInfo.topActivityLetterboxHeight = top.getBounds().height();
+ }
+ if (appCompatTaskInfo.isLetterboxDoubleTapEnabled) {
if (appCompatTaskInfo.isTopActivityPillarboxed()) {
// Pillarboxed
appCompatTaskInfo.topActivityLetterboxHorizontalPosition =
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
index 94caf28..c8a6545 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
@@ -17,6 +17,7 @@
package com.android.server.permission.access.appop
import android.app.AppOpsManager
+import android.util.Slog
import com.android.server.permission.access.GetStateScope
import com.android.server.permission.access.MutableAccessState
import com.android.server.permission.access.MutateStateScope
@@ -84,6 +85,10 @@
appOpName: String,
mode: Int
): Boolean {
+ if (userId !in newState.userStates) {
+ Slog.e(LOG_TAG, "Unable to set app op mode for missing user $userId")
+ return false
+ }
val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
val oldMode =
newState.userStates[userId]!!
@@ -152,4 +157,8 @@
*/
abstract fun onStateMutated()
}
+
+ companion object {
+ private val LOG_TAG = AppIdAppOpPolicy::class.java.simpleName
+ }
}
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
index 8f464d4..3ee7430 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
@@ -17,29 +17,62 @@
package com.android.server.permission.access.appop
import android.app.AppOpsManager
+import android.os.Binder
import android.os.Handler
import android.os.UserHandle
+import android.permission.flags.Flags
import android.util.ArrayMap
import android.util.ArraySet
+import android.util.LongSparseArray
+import android.util.Slog
+import android.util.SparseArray
import android.util.SparseBooleanArray
import android.util.SparseIntArray
import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.util.IntPair
import com.android.server.appop.AppOpsCheckingServiceInterface
import com.android.server.appop.AppOpsCheckingServiceInterface.AppOpsModeChangedListener
import com.android.server.permission.access.AccessCheckingService
import com.android.server.permission.access.AppOpUri
+import com.android.server.permission.access.GetStateScope
import com.android.server.permission.access.PackageUri
+import com.android.server.permission.access.PermissionUri
import com.android.server.permission.access.UidUri
+import com.android.server.permission.access.appop.AppOpModes.MODE_ALLOWED
+import com.android.server.permission.access.appop.AppOpModes.MODE_FOREGROUND
+import com.android.server.permission.access.appop.AppOpModes.MODE_IGNORED
import com.android.server.permission.access.collection.forEachIndexed
import com.android.server.permission.access.collection.set
+import com.android.server.permission.access.permission.AppIdPermissionPolicy
+import com.android.server.permission.access.permission.PermissionFlags
+import com.android.server.permission.access.permission.PermissionService
class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingServiceInterface {
private val packagePolicy =
service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME) as PackageAppOpPolicy
private val appIdPolicy =
service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy
+ private val permissionPolicy =
+ service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy
private val context = service.context
+
+ // Maps appop code to its runtime permission
+ private val runtimeAppOpToPermissionNames = SparseArray<String>()
+
+ // Maps runtime permission to its appop codes
+ private val runtimePermissionNameToAppOp = ArrayMap<String, Int>()
+
+ private var foregroundableOps = SparseBooleanArray()
+
+ /* Maps foreground permissions to their background permission. Background permissions aren't
+ required to be runtime */
+ private val foregroundToBackgroundPermissionName = ArrayMap<String, String>()
+
+ /* Maps background permissions to their foreground permissions. Background permissions aren't
+ required to be runtime */
+ private val backgroundToForegroundPermissionNames = ArrayMap<String, ArraySet<String>>()
+
private lateinit var handler: Handler
@Volatile private var listeners = ArraySet<AppOpsModeChangedListener>()
@@ -68,11 +101,58 @@
}
override fun systemReady() {
- // Not implemented because upgrades are handled automatically.
+ if (useRuntimePermissionAppOpMapping()) {
+ createPermissionAppOpMapping()
+ permissionPolicy.addOnPermissionFlagsChangedListener(OnPermissionFlagsChangedListener())
+ }
+ }
+
+ private fun createPermissionAppOpMapping() {
+ val permissions = service.getState { with(permissionPolicy) { getPermissions() } }
+
+ for (appOpCode in 0 until AppOpsManager._NUM_OP) {
+ AppOpsManager.opToPermission(appOpCode)?.let { permissionName ->
+ // Multiple ops might map to a single permission but only one is considered the
+ // runtime appop calculations.
+ if (appOpCode == AppOpsManager.permissionToOpCode(permissionName)) {
+ val permission = permissions[permissionName]!!
+ if (permission.isRuntime) {
+ runtimePermissionNameToAppOp[permissionName] = appOpCode
+ runtimeAppOpToPermissionNames[appOpCode] = permissionName
+ permission.permissionInfo.backgroundPermission?.let {
+ backgroundPermissionName ->
+ // Note: background permission may not be runtime,
+ // e.g. microphone/camera.
+ foregroundableOps[appOpCode] = true
+ foregroundToBackgroundPermissionName[permissionName] =
+ backgroundPermissionName
+ backgroundToForegroundPermissionNames
+ .getOrPut(backgroundPermissionName, ::ArraySet)
+ .add(permissionName)
+ }
+ }
+ }
+ }
+ }
}
override fun getNonDefaultUidModes(uid: Int, persistentDeviceId: String): SparseIntArray {
- return opNameMapToOpSparseArray(getUidModes(uid))
+ val appId = UserHandle.getAppId(uid)
+ val userId = UserHandle.getUserId(uid)
+ service.getState {
+ val modes =
+ with(appIdPolicy) { opNameMapToOpSparseArray(getAppOpModes(appId, userId)?.map) }
+ if (useRuntimePermissionAppOpMapping()) {
+ runtimePermissionNameToAppOp.forEachIndexed { _, permissionName, appOpCode ->
+ val mode = getUidModeFromPermissionState(appId, userId, permissionName)
+ if (mode != AppOpsManager.opToDefaultMode(appOpCode)) {
+ modes[appOpCode] = mode
+ }
+ }
+ }
+
+ return modes
+ }
}
override fun getNonDefaultPackageModes(packageName: String, userId: Int): SparseIntArray {
@@ -83,7 +163,13 @@
val appId = UserHandle.getAppId(uid)
val userId = UserHandle.getUserId(uid)
val opName = AppOpsManager.opToPublicName(op)
- return service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } }
+ val permissionName = runtimeAppOpToPermissionNames[op]
+
+ return if (!useRuntimePermissionAppOpMapping() || permissionName == null) {
+ service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } }
+ } else {
+ service.getState { getUidModeFromPermissionState(appId, userId, permissionName) }
+ }
}
private fun getUidModes(uid: Int): ArrayMap<String, Int>? {
@@ -92,13 +178,63 @@
return service.getState { with(appIdPolicy) { getAppOpModes(appId, userId) } }?.map
}
- override fun setUidMode(uid: Int, persistentDeviceId: String, op: Int, mode: Int): Boolean {
+ private fun GetStateScope.getUidModeFromPermissionState(
+ appId: Int,
+ userId: Int,
+ permissionName: String
+ ): Int {
+ val permissionFlags =
+ with(permissionPolicy) { getPermissionFlags(appId, userId, permissionName) }
+ val backgroundPermissionName = foregroundToBackgroundPermissionName[permissionName]
+ val backgroundPermissionFlags =
+ if (backgroundPermissionName != null) {
+ with(permissionPolicy) {
+ getPermissionFlags(appId, userId, backgroundPermissionName)
+ }
+ } else {
+ PermissionFlags.RUNTIME_GRANTED
+ }
+ val result = evaluateModeFromPermissionFlags(permissionFlags, backgroundPermissionFlags)
+ if (result != MODE_IGNORED) {
+ return result
+ }
+
+ val fullerPermissionName =
+ PermissionService.getFullerPermission(permissionName) ?: return result
+ return getUidModeFromPermissionState(appId, userId, fullerPermissionName)
+ }
+
+ private fun evaluateModeFromPermissionFlags(
+ foregroundFlags: Int,
+ backgroundFlags: Int = PermissionFlags.RUNTIME_GRANTED
+ ): Int =
+ if (PermissionFlags.isAppOpGranted(foregroundFlags)) {
+ if (PermissionFlags.isAppOpGranted(backgroundFlags)) {
+ MODE_ALLOWED
+ } else {
+ MODE_FOREGROUND
+ }
+ } else {
+ MODE_IGNORED
+ }
+
+ override fun setUidMode(uid: Int, persistentDeviceId: String, code: Int, mode: Int): Boolean {
+ if (useRuntimePermissionAppOpMapping() && code in runtimeAppOpToPermissionNames) {
+ Slog.w(
+ LOG_TAG,
+ "Cannot set UID mode for runtime permission app op, uid = $uid," +
+ " code = ${AppOpsManager.opToName(code)}, mode = ${AppOpsManager.modeToName(mode)}",
+ RuntimeException()
+ )
+ return false
+ }
+
val appId = UserHandle.getAppId(uid)
val userId = UserHandle.getUserId(uid)
- val opName = AppOpsManager.opToPublicName(op)
- var wasChanged = false
+ val appOpName = AppOpsManager.opToPublicName(code)
+ var wasChanged: Boolean
service.mutateState {
- wasChanged = with(appIdPolicy) { setAppOpMode(appId, userId, opName, mode) }
+ wasChanged = with(appIdPolicy) { setAppOpMode(appId, userId, appOpName, mode) }
}
return wasChanged
}
@@ -113,10 +249,22 @@
private fun getPackageModes(packageName: String, userId: Int): ArrayMap<String, Int>? =
service.getState { with(packagePolicy) { getAppOpModes(packageName, userId) } }?.map
- override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) {
- val opName = AppOpsManager.opToPublicName(op)
+ override fun setPackageMode(packageName: String, appOpCode: Int, mode: Int, userId: Int) {
+ val appOpName = AppOpsManager.opToPublicName(appOpCode)
+
+ if (
+ useRuntimePermissionAppOpMapping() && runtimeAppOpToPermissionNames.contains(appOpCode)
+ ) {
+ Slog.w(
+ LOG_TAG,
+ "(packageName=$packageName, userId=$userId)'s appop state" +
+ " for runtime op $appOpName should not be set directly.",
+ RuntimeException()
+ )
+ return
+ }
service.mutateState {
- with(packagePolicy) { setAppOpMode(packageName, userId, opName, mode) }
+ with(packagePolicy) { setAppOpMode(packageName, userId, appOpName, mode) }
}
}
@@ -127,7 +275,7 @@
}
override fun removePackage(packageName: String, userId: Int): Boolean {
- var wasChanged = false
+ var wasChanged: Boolean
service.mutateState {
wasChanged = with(packagePolicy) { removeAppOpModes(packageName, userId) }
}
@@ -157,6 +305,13 @@
this[AppOpsManager.strOpToOp(op)] = true
}
}
+ if (useRuntimePermissionAppOpMapping()) {
+ foregroundableOps.forEachIndexed { _, op, _ ->
+ if (getUidMode(uid, persistentDeviceId, op) == AppOpsManager.MODE_FOREGROUND) {
+ this[op] = true
+ }
+ }
+ }
}
}
@@ -167,6 +322,13 @@
this[AppOpsManager.strOpToOp(op)] = true
}
}
+ if (useRuntimePermissionAppOpMapping()) {
+ foregroundableOps.forEachIndexed { _, op, _ ->
+ if (getPackageMode(packageName, op, userId) == AppOpsManager.MODE_FOREGROUND) {
+ this[op] = true
+ }
+ }
+ }
}
}
@@ -188,9 +350,10 @@
}
}
- inner class OnAppIdAppOpModeChangedListener : AppIdAppOpPolicy.OnAppOpModeChangedListener() {
+ private inner class OnAppIdAppOpModeChangedListener :
+ AppIdAppOpPolicy.OnAppOpModeChangedListener() {
// (uid, appOpCode) -> newMode
- val pendingChanges = ArrayMap<Pair<Int, Int>, Int>()
+ private val pendingChanges = LongSparseArray<Int>()
override fun onAppOpModeChanged(
appId: Int,
@@ -201,7 +364,7 @@
) {
val uid = UserHandle.getUid(userId, appId)
val appOpCode = AppOpsManager.strOpToOp(appOpName)
- val key = Pair(uid, appOpCode)
+ val key = IntPair.of(uid, appOpCode)
pendingChanges[key] = newMode
}
@@ -210,8 +373,8 @@
val listenersLocal = listeners
pendingChanges.forEachIndexed { _, key, mode ->
listenersLocal.forEachIndexed { _, listener ->
- val uid = key.first
- val appOpCode = key.second
+ val uid = IntPair.first(key)
+ val appOpCode = IntPair.second(key)
listener.onUidModeChanged(uid, appOpCode, mode)
}
@@ -224,7 +387,7 @@
private inner class OnPackageAppOpModeChangedListener :
PackageAppOpPolicy.OnAppOpModeChangedListener() {
// (packageName, userId, appOpCode) -> newMode
- val pendingChanges = ArrayMap<Triple<String, Int, Int>, Int>()
+ private val pendingChanges = ArrayMap<Triple<String, Int, Int>, Int>()
override fun onAppOpModeChanged(
packageName: String,
@@ -254,4 +417,115 @@
pendingChanges.clear()
}
}
+
+ private inner class OnPermissionFlagsChangedListener :
+ AppIdPermissionPolicy.OnPermissionFlagsChangedListener {
+ // (uid, appOpCode) -> newMode
+ private val pendingChanges = LongSparseArray<Int>()
+
+ override fun onPermissionFlagsChanged(
+ appId: Int,
+ userId: Int,
+ permissionName: String,
+ oldFlags: Int,
+ newFlags: Int
+ ) {
+ backgroundToForegroundPermissionNames[permissionName]?.let { foregroundPermissions ->
+ // This is a background permission; there may be multiple foreground permissions
+ // affected.
+ foregroundPermissions.forEachIndexed { _, foregroundPermissionName ->
+ runtimePermissionNameToAppOp[foregroundPermissionName]?.let { appOpCode ->
+ val foregroundPermissionFlags =
+ getPermissionFlags(appId, userId, foregroundPermissionName)
+ addPendingChangedModeIfNeeded(
+ appId,
+ userId,
+ appOpCode,
+ foregroundPermissionFlags,
+ oldFlags,
+ foregroundPermissionFlags,
+ newFlags
+ )
+ }
+ }
+ }
+ ?: foregroundToBackgroundPermissionName[permissionName]?.let { backgroundPermission
+ ->
+ runtimePermissionNameToAppOp[permissionName]?.let { appOpCode ->
+ val backgroundPermissionFlags =
+ getPermissionFlags(appId, userId, backgroundPermission)
+ addPendingChangedModeIfNeeded(
+ appId,
+ userId,
+ appOpCode,
+ oldFlags,
+ backgroundPermissionFlags,
+ newFlags,
+ backgroundPermissionFlags
+ )
+ }
+ }
+ ?: runtimePermissionNameToAppOp[permissionName]?.let { appOpCode ->
+ addPendingChangedModeIfNeeded(
+ appId,
+ userId,
+ appOpCode,
+ oldFlags,
+ PermissionFlags.RUNTIME_GRANTED,
+ newFlags,
+ PermissionFlags.RUNTIME_GRANTED
+ )
+ }
+ }
+
+ private fun getPermissionFlags(appId: Int, userId: Int, permissionName: String): Int =
+ service.getState {
+ with(permissionPolicy) { getPermissionFlags(appId, userId, permissionName) }
+ }
+
+ private fun addPendingChangedModeIfNeeded(
+ appId: Int,
+ userId: Int,
+ appOpCode: Int,
+ oldForegroundFlags: Int,
+ oldBackgroundFlags: Int,
+ newForegroundFlags: Int,
+ newBackgroundFlags: Int,
+ ) {
+ val oldMode = evaluateModeFromPermissionFlags(oldForegroundFlags, oldBackgroundFlags)
+ val newMode = evaluateModeFromPermissionFlags(newForegroundFlags, newBackgroundFlags)
+
+ if (oldMode != newMode) {
+ val uid = UserHandle.getUid(userId, appId)
+ pendingChanges[IntPair.of(uid, appOpCode)] = newMode
+ }
+ }
+
+ override fun onStateMutated() {
+ val listenersLocal = listeners
+ pendingChanges.forEachIndexed { _, key, mode ->
+ listenersLocal.forEachIndexed { _, listener ->
+ val uid = IntPair.first(key)
+ val appOpCode = IntPair.second(key)
+
+ listener.onUidModeChanged(uid, appOpCode, mode)
+ }
+ }
+
+ pendingChanges.clear()
+ }
+ }
+
+ companion object {
+ private val LOG_TAG = AppOpService::class.java.simpleName
+
+ private fun useRuntimePermissionAppOpMapping(): Boolean {
+ val token = Binder.clearCallingIdentity()
+ return try {
+ Flags.runtimePermissionAppopsMapping()
+ } finally {
+ Binder.restoreCallingIdentity(token)
+ }
+ }
+ }
}
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
index 0d9470e..2f15dc7 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
@@ -17,6 +17,7 @@
package com.android.server.permission.access.appop
import android.app.AppOpsManager
+import android.util.Slog
import com.android.server.permission.access.GetStateScope
import com.android.server.permission.access.MutableAccessState
import com.android.server.permission.access.MutateStateScope
@@ -87,6 +88,10 @@
appOpName: String,
mode: Int
): Boolean {
+ if (userId !in newState.userStates) {
+ Slog.e(LOG_TAG, "Unable to set app op mode for missing user $userId")
+ return false
+ }
val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
val oldMode =
newState.userStates[userId]!!
@@ -155,4 +160,8 @@
*/
abstract fun onStateMutated()
}
+
+ companion object {
+ private val LOG_TAG = PackageAppOpPolicy::class.java.simpleName
+ }
}
diff --git a/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt
new file mode 100644
index 0000000..827dd0e
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.collection
+
+import android.util.LongSparseArray
+
+inline fun <T> LongSparseArray<T>.allIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
+inline fun <T> LongSparseArray<T>.anyIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return true
+ }
+ }
+ return false
+}
+
+inline fun <T> LongSparseArray<T>.forEachIndexed(action: (Int, Long, T) -> Unit) {
+ for (index in 0 until size) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
+inline fun <T> LongSparseArray<T>.forEachReversedIndexed(action: (Int, Long, T) -> Unit) {
+ for (index in lastIndex downTo 0) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
+inline fun <T> LongSparseArray<T>.getOrPut(key: Long, defaultValue: () -> T): T {
+ val index = indexOfKey(key)
+ return if (index >= 0) {
+ valueAt(index)
+ } else {
+ defaultValue().also { put(key, it) }
+ }
+}
+
+inline val <T> LongSparseArray<T>.lastIndex: Int
+ get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> LongSparseArray<T>.minusAssign(key: Long) {
+ delete(key)
+}
+
+inline fun <T> LongSparseArray<T>.noneIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
+inline fun <T> LongSparseArray<T>.removeAllIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ var isChanged = false
+ forEachReversedIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ removeAt(index)
+ isChanged = true
+ }
+ }
+ return isChanged
+}
+
+inline fun <T> LongSparseArray<T>.retainAllIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ var isChanged = false
+ forEachReversedIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
+ removeAt(index)
+ isChanged = true
+ }
+ }
+ return isChanged
+}
+
+inline val <T> LongSparseArray<T>.size: Int
+ get() = size()
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> LongSparseArray<T>.set(key: Long, value: T) {
+ put(key, value)
+}
diff --git a/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt
new file mode 100644
index 0000000..a582431
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.permission.access.collection
+
+import android.util.SparseIntArray
+
+inline fun SparseIntArray.allIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
+inline fun SparseIntArray.anyIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return true
+ }
+ }
+ return false
+}
+
+inline fun SparseIntArray.forEachIndexed(action: (Int, Int, Int) -> Unit) {
+ for (index in 0 until size) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
+inline fun SparseIntArray.forEachReversedIndexed(action: (Int, Int, Int) -> Unit) {
+ for (index in lastIndex downTo 0) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
+inline fun SparseIntArray.getOrPut(key: Int, defaultValue: () -> Int): Int {
+ val index = indexOfKey(key)
+ return if (index >= 0) {
+ valueAt(index)
+ } else {
+ defaultValue().also { put(key, it) }
+ }
+}
+
+inline val SparseIntArray.lastIndex: Int
+ get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun SparseIntArray.minusAssign(key: Int) {
+ delete(key)
+}
+
+inline fun SparseIntArray.noneIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
+fun SparseIntArray.remove(key: Int) {
+ delete(key)
+}
+
+fun SparseIntArray.remove(key: Int, defaultValue: Int): Int {
+ val index = indexOfKey(key)
+ return if (index >= 0) {
+ val oldValue = valueAt(index)
+ removeAt(index)
+ oldValue
+ } else {
+ defaultValue
+ }
+}
+
+inline fun SparseIntArray.removeAllIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ var isChanged = false
+ forEachReversedIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ removeAt(index)
+ isChanged = true
+ }
+ }
+ return isChanged
+}
+
+inline fun SparseIntArray.retainAllIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ var isChanged = false
+ forEachReversedIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
+ removeAt(index)
+ isChanged = true
+ }
+ }
+ return isChanged
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun SparseIntArray.set(key: Int, value: Int) {
+ put(key, value)
+}
+
+inline val SparseIntArray.size: Int
+ get() = size()
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 022268d..62d2d7e 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -134,7 +134,9 @@
) {
val changedPermissionNames = MutableIndexedSet<String>()
packageNames.forEachIndexed { _, packageName ->
- val packageState = newState.externalState.packageStates[packageName]!!
+ // The package may still be removed even if it was once notified as installed.
+ val packageState = newState.externalState.packageStates[packageName]
+ ?: return@forEachIndexed
adoptPermissions(packageState, changedPermissionNames)
addPermissionGroups(packageState)
addPermissions(packageState, changedPermissionNames)
@@ -147,12 +149,14 @@
}
packageNames.forEachIndexed { _, packageName ->
- val packageState = newState.externalState.packageStates[packageName]!!
+ val packageState = newState.externalState.packageStates[packageName]
+ ?: return@forEachIndexed
val installedPackageState = if (isSystemUpdated) packageState else null
evaluateAllPermissionStatesForPackage(packageState, installedPackageState)
}
packageNames.forEachIndexed { _, packageName ->
- val packageState = newState.externalState.packageStates[packageName]!!
+ val packageState = newState.externalState.packageStates[packageName]
+ ?: return@forEachIndexed
newState.externalState.userIds.forEachIndexed { _, userId ->
inheritImplicitPermissionStates(packageState.appId, userId)
}
@@ -1607,6 +1611,13 @@
flagMask: Int,
flagValues: Int
): Boolean {
+ if (userId !in newState.userStates) {
+ // Despite that we check UserManagerInternal.exists() in PermissionService, we may still
+ // sometimes get race conditions between that check and the actual mutateState() call.
+ // This should rarely happen but at least we should not crash.
+ Slog.e(LOG_TAG, "Unable to update permission flags for missing user $userId")
+ return false
+ }
val oldFlags =
newState.userStates[userId]!!
.appIdPermissionFlags[appId]
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index f469ab5..b162a1b 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -2870,5 +2870,8 @@
} else {
emptySet<String>()
}
+
+ fun getFullerPermission(permissionName: String): String? =
+ FULLER_PERMISSIONS[permissionName]
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 116d5db..97767a5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -426,6 +426,8 @@
@Test
public void testGetNextConstraintDropTimeElapsedLocked() {
+ setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 200 * HOUR_IN_MILLIS);
+
long nextTimeToDropNumConstraints;
// no delay, deadline
@@ -457,15 +459,18 @@
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(130400100, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) / 2,
+ nextTimeToDropNumConstraints);
js.setNumDroppedFlexibleConstraints(1);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(156320100L, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) * 6 / 10,
+ nextTimeToDropNumConstraints);
js.setNumDroppedFlexibleConstraints(2);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(182240100L, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) * 7 / 10,
+ nextTimeToDropNumConstraints);
// no delay, no deadline
jb = createJob(0);
@@ -473,15 +478,15 @@
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(129600100, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) / 2, nextTimeToDropNumConstraints);
js.setNumDroppedFlexibleConstraints(1);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(155520100L, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) * 6 / 10, nextTimeToDropNumConstraints);
js.setNumDroppedFlexibleConstraints(2);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(181440100L, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) * 7 / 10, nextTimeToDropNumConstraints);
// delay, deadline
jb = createJob(0)
@@ -1328,7 +1333,7 @@
final ArraySet<String> pkgs = new ArraySet<>();
pkgs.add(js.getSourcePackageName());
- when(mJobSchedulerService.getPackagesForUidLocked(mSourceUid)).thenReturn(pkgs);
+ doReturn(pkgs).when(mJobSchedulerService).getPackagesForUidLocked(mSourceUid);
setUidBias(mSourceUid, BIAS_TOP_APP);
setUidBias(mSourceUid, BIAS_FOREGROUND_SERVICE);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index fd6aa0c..e6298ee 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -19,9 +19,11 @@
import static android.os.UserManager.DISALLOW_SMS;
import static android.os.UserManager.DISALLOW_USER_SWITCH;
import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
+import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -30,20 +32,27 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
+import android.app.KeyguardManager;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
+import android.multiuser.Flags;
+import android.os.PowerManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.util.Log;
import android.util.Pair;
@@ -52,6 +61,7 @@
import androidx.test.annotation.UiThreadTest;
+import com.android.dx.mockito.inline.extended.MockedVoidMethod;
import com.android.internal.widget.LockSettingsInternal;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.testing.ExtendedMockitoRule;
@@ -65,6 +75,7 @@
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -115,8 +126,11 @@
.spyStatic(LocalServices.class)
.spyStatic(SystemProperties.class)
.mockStatic(Settings.Global.class)
+ .mockStatic(Settings.Secure.class)
.build();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private final Object mPackagesLock = new Object();
private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation()
.getTargetContext();
@@ -133,6 +147,8 @@
private @Mock StorageManager mStorageManager;
private @Mock LockSettingsInternal mLockSettingsInternal;
private @Mock PackageManagerInternal mPackageManagerInternal;
+ private @Mock KeyguardManager mKeyguardManager;
+ private @Mock PowerManager mPowerManager;
/**
* Reference to the {@link UserManagerService} being tested.
@@ -156,6 +172,8 @@
when(mDeviceStorageMonitorInternal.isMemoryLow()).thenReturn(false);
mockGetLocalService(DeviceStorageMonitorInternal.class, mDeviceStorageMonitorInternal);
when(mSpiedContext.getSystemService(StorageManager.class)).thenReturn(mStorageManager);
+ when(mSpiedContext.getSystemService(KeyguardManager.class)).thenReturn(mKeyguardManager);
+ when(mSpiedContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
mockGetLocalService(LockSettingsInternal.class, mLockSettingsInternal);
mockGetLocalService(PackageManagerInternal.class, mPackageManagerInternal);
doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
@@ -550,6 +568,143 @@
assertTrue(hasRestrictionsInUserXMLFile(user.id));
}
+ @Test
+ public void testAutoLockOnDeviceLockForPrivateProfile() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+ UserManagerService mSpiedUms = spy(mUms);
+ UserInfo privateProfileUser =
+ mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+ mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
+ Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync(
+ eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true), any(),
+ any());
+
+ mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(true);
+
+ Mockito.verify(mSpiedUms).setQuietModeEnabledAsync(
+ eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true),
+ any(), any());
+ }
+
+ @Test
+ public void testAutoLockOnDeviceLockForPrivateProfile_keyguardUnlocked() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+ UserManagerService mSpiedUms = spy(mUms);
+ UserInfo privateProfileUser =
+ mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+ mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
+
+ mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(false);
+
+ // Verify that no operation to disable quiet mode is not called
+ Mockito.verify(mSpiedUms, never()).setQuietModeEnabledAsync(
+ eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true),
+ any(), any());
+ }
+
+ @Test
+ public void testAutoLockOnDeviceLockForPrivateProfile_flagDisabled() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+ UserManagerService mSpiedUms = spy(mUms);
+ UserInfo privateProfileUser =
+ mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+
+ mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(true);
+
+ // Verify that no auto-lock operations take place
+ verify((MockedVoidMethod) () -> Settings.Secure.getInt(any(),
+ eq(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK), anyInt()), never());
+ Mockito.verify(mSpiedUms, never()).setQuietModeEnabledAsync(
+ eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true),
+ any(), any());
+ }
+
+ @Test
+ public void testAutoLockAfterInactityForPrivateProfile() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+ UserManagerService mSpiedUms = spy(mUms);
+ mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
+ when(mPowerManager.isInteractive()).thenReturn(false);
+
+ UserInfo privateProfileUser =
+ mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+ Mockito.doNothing().when(mSpiedUms).scheduleMessageToAutoLockPrivateSpace(
+ eq(privateProfileUser.getUserHandle().getIdentifier()), any(),
+ anyLong());
+
+
+ mSpiedUms.maybeScheduleMessageToAutoLockPrivateSpace();
+
+ Mockito.verify(mSpiedUms).scheduleMessageToAutoLockPrivateSpace(
+ eq(privateProfileUser.getUserHandle().getIdentifier()), any(), anyLong());
+ }
+
+ @Test
+ public void testSetOrUpdateAutoLockPreference_noPrivateProfile() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+
+ mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
+
+ Mockito.verify(mSpiedContext, never()).registerReceiver(any(), any(), any(), any());
+ Mockito.verify(mSpiedContext, never()).unregisterReceiver(any());
+ Mockito.verify(mKeyguardManager, never()).removeKeyguardLockedStateListener((any()));
+ Mockito.verify(mKeyguardManager, never()).addKeyguardLockedStateListener(any(), any());
+ }
+
+ @Test
+ public void testSetOrUpdateAutoLockPreference() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+ mUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+
+ // Set the preference to auto lock on device lock
+ mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
+
+ // Verify that keyguard state listener was added
+ Mockito.verify(mKeyguardManager).addKeyguardLockedStateListener(any(), any());
+ //Verity that keyguard state listener was not removed
+ Mockito.verify(mKeyguardManager, never()).removeKeyguardLockedStateListener(any());
+ // Broadcasts are already unregistered when UserManagerService starts and the flag
+ // isDeviceInactivityBroadcastReceiverRegistered is false
+ Mockito.verify(mSpiedContext, never()).registerReceiver(any(), any(), any(), any());
+ Mockito.verify(mSpiedContext, never()).unregisterReceiver(any());
+
+ Mockito.clearInvocations(mKeyguardManager);
+ Mockito.clearInvocations(mSpiedContext);
+
+ // Now set the preference to auto-lock on inactivity
+ mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
+
+ // Verify that inactivity broadcasts are registered
+ Mockito.verify(mSpiedContext, times(2)).registerReceiver(any(), any(), any(), any());
+ // Verify that keyguard state listener is removed
+ Mockito.verify(mKeyguardManager).removeKeyguardLockedStateListener(any());
+ // Verify that all other operations don't take place
+ Mockito.verify(mSpiedContext, never()).unregisterReceiver(any());
+ Mockito.verify(mKeyguardManager, never()).addKeyguardLockedStateListener(any(), any());
+
+ Mockito.clearInvocations(mKeyguardManager);
+ Mockito.clearInvocations(mSpiedContext);
+
+ // Finally, set the preference to don't auto-lock
+ mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER);
+
+ // Verify that inactivity broadcasts are unregistered and keyguard listener was removed
+ Mockito.verify(mSpiedContext).unregisterReceiver(any());
+ Mockito.verify(mKeyguardManager).removeKeyguardLockedStateListener(any());
+ // Verify that no broadcasts were registered and no listeners were added
+ Mockito.verify(mSpiedContext, never()).registerReceiver(any(), any(), any(), any());
+ Mockito.verify(mKeyguardManager, never()).addKeyguardLockedStateListener(any(), any());
+ }
+
/**
* Returns true if the user's XML file has Default restrictions
* @param userId Id of the user.
@@ -632,6 +787,12 @@
SystemProperties.getBoolean(eq("fw.show_multiuserui"), anyBoolean()));
}
+ private void mockAutoLockForPrivateSpace(int val) {
+ doReturn(val).when(() ->
+ Settings.Secure.getIntForUser(any(), eq(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK),
+ anyInt(), anyInt()));
+ }
+
private void mockCurrentUser(@UserIdInt int userId) {
mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 0045026..0831086 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -29,8 +29,6 @@
"src/**/*.java",
"src/**/*.kt",
- "test-apps/JobTestApp/src/**/*.java",
-
"test-apps/SuspendTestApp/src/**/*.java",
],
static_libs: [
@@ -124,7 +122,6 @@
},
data: [
- ":JobTestApp",
":SimpleServiceTestApp1",
":SimpleServiceTestApp2",
":SimpleServiceTestApp3",
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index b1d5039..27c522d 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -29,7 +29,6 @@
<option name="cleanup-apks" value="true" />
<option name="install-arg" value="-t" />
<option name="test-file-name" value="FrameworksServicesTests.apk" />
- <option name="test-file-name" value="JobTestApp.apk" />
<option name="test-file-name" value="SuspendTestApp.apk" />
<option name="test-file-name" value="SimpleServiceTestApp1.apk" />
<option name="test-file-name" value="SimpleServiceTestApp2.apk" />
diff --git a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
deleted file mode 100644
index e871fc5..0000000
--- a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2017 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.job;
-
-import static com.android.servicestests.apps.jobtestapp.TestJobService.ACTION_JOB_STARTED;
-import static com.android.servicestests.apps.jobtestapp.TestJobService.ACTION_JOB_STOPPED;
-import static com.android.servicestests.apps.jobtestapp.TestJobService.JOB_PARAMS_EXTRA_KEY;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.app.ActivityManager;
-import android.app.AppOpsManager;
-import android.app.IActivityManager;
-import android.app.job.JobParameters;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.IDeviceIdleController;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.servicestests.apps.jobtestapp.TestJobActivity;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests that background restrictions on jobs work as expected.
- * This test requires test-apps/JobTestApp to be installed on the device.
- * To run this test from root of checkout:
- * <pre>
- * mmm -j32 frameworks/base/services/tests/servicestests/
- * adb install -r $OUT/data/app/JobTestApp/JobTestApp.apk
- * adb install -r $OUT/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
- * adb shell am instrument -e class 'com.android.server.job.BackgroundRestrictionsTest' -w \
- * com.android.frameworks.servicestests
- * </pre>
- */
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class BackgroundRestrictionsTest {
- private static final String TAG = BackgroundRestrictionsTest.class.getSimpleName();
- private static final String TEST_APP_PACKAGE = "com.android.servicestests.apps.jobtestapp";
- private static final String TEST_APP_ACTIVITY = TEST_APP_PACKAGE + ".TestJobActivity";
- private static final long POLL_INTERVAL = 500;
- private static final long DEFAULT_WAIT_TIMEOUT = 10_000;
-
- private Context mContext;
- private AppOpsManager mAppOpsManager;
- private IDeviceIdleController mDeviceIdleController;
- private IActivityManager mIActivityManager;
- private volatile int mTestJobId = -1;
- private int mTestPackageUid;
- /* accesses must be synchronized on itself */
- private final TestJobStatus mTestJobStatus = new TestJobStatus();
- private final BroadcastReceiver mJobStateChangeReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final JobParameters params = intent.getParcelableExtra(JOB_PARAMS_EXTRA_KEY);
- Log.d(TAG, "Received action " + intent.getAction());
- synchronized (mTestJobStatus) {
- switch (intent.getAction()) {
- case ACTION_JOB_STARTED:
- mTestJobStatus.running = true;
- mTestJobStatus.jobId = params.getJobId();
- mTestJobStatus.stopReason = JobParameters.STOP_REASON_UNDEFINED;
- break;
- case ACTION_JOB_STOPPED:
- mTestJobStatus.running = false;
- mTestJobStatus.jobId = params.getJobId();
- mTestJobStatus.stopReason = params.getStopReason();
- break;
- }
- }
- }
- };
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
- mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
- ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
- mIActivityManager = ActivityManager.getService();
- mTestPackageUid = mContext.getPackageManager().getPackageUid(TEST_APP_PACKAGE, 0);
- mTestJobStatus.reset();
- final IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(ACTION_JOB_STARTED);
- intentFilter.addAction(ACTION_JOB_STOPPED);
- mContext.registerReceiver(mJobStateChangeReceiver, intentFilter,
- Context.RECEIVER_EXPORTED_UNAUDITED);
- setAppOpsModeAllowed(true);
- setPowerExemption(false);
- }
-
- private void scheduleTestJob() {
- mTestJobId = (int) (SystemClock.uptimeMillis() / 1000);
- final Intent scheduleJobIntent = new Intent(TestJobActivity.ACTION_START_JOB);
- scheduleJobIntent.putExtra(TestJobActivity.EXTRA_JOB_ID_KEY, mTestJobId);
- scheduleJobIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- scheduleJobIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY));
- mContext.startActivity(scheduleJobIntent);
- }
-
- private void scheduleAndAssertJobStarted() throws Exception {
- scheduleTestJob();
- Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY);
- assertTrue("Job did not start after scheduling", awaitJobStart(DEFAULT_WAIT_TIMEOUT));
- }
-
- @FlakyTest
- @Test
- public void testPowerExemption() throws Exception {
- scheduleAndAssertJobStarted();
- setAppOpsModeAllowed(false);
- mIActivityManager.makePackageIdle(TEST_APP_PACKAGE, UserHandle.USER_CURRENT);
- assertTrue("Job did not stop after putting app under bg-restriction",
- awaitJobStop(DEFAULT_WAIT_TIMEOUT,
- JobParameters.STOP_REASON_BACKGROUND_RESTRICTION));
-
- setPowerExemption(true);
- scheduleTestJob();
- Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY);
- assertTrue("Job did not start when the app was in the power exemption list",
- awaitJobStart(DEFAULT_WAIT_TIMEOUT));
-
- setPowerExemption(false);
- assertTrue("Job did not stop after removing from the power exemption list",
- awaitJobStop(DEFAULT_WAIT_TIMEOUT,
- JobParameters.STOP_REASON_BACKGROUND_RESTRICTION));
-
- scheduleTestJob();
- Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY);
- assertFalse("Job started under bg-restrictions", awaitJobStart(DEFAULT_WAIT_TIMEOUT));
- setPowerExemption(true);
- assertTrue("Job did not start when the app was in the power exemption list",
- awaitJobStart(DEFAULT_WAIT_TIMEOUT));
- }
-
- @After
- public void tearDown() throws Exception {
- final Intent cancelJobsIntent = new Intent(TestJobActivity.ACTION_CANCEL_JOBS);
- cancelJobsIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY));
- cancelJobsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(cancelJobsIntent);
- mContext.unregisterReceiver(mJobStateChangeReceiver);
- Thread.sleep(500); // To avoid race with register in the next setUp
- setAppOpsModeAllowed(true);
- setPowerExemption(false);
- }
-
- private void setPowerExemption(boolean exempt) throws RemoteException {
- if (exempt) {
- mDeviceIdleController.addPowerSaveWhitelistApp(TEST_APP_PACKAGE);
- } else {
- mDeviceIdleController.removePowerSaveWhitelistApp(TEST_APP_PACKAGE);
- }
- }
-
- private void setAppOpsModeAllowed(boolean allow) {
- mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mTestPackageUid,
- TEST_APP_PACKAGE, allow ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
- }
-
- private boolean awaitJobStart(long timeout) throws InterruptedException {
- return waitUntilTrue(timeout, () -> {
- synchronized (mTestJobStatus) {
- return (mTestJobStatus.jobId == mTestJobId) && mTestJobStatus.running;
- }
- });
- }
-
- private boolean awaitJobStop(long timeout, @JobParameters.StopReason int expectedStopReason)
- throws InterruptedException {
- return waitUntilTrue(timeout, () -> {
- synchronized (mTestJobStatus) {
- return (mTestJobStatus.jobId == mTestJobId) && !mTestJobStatus.running
- && (expectedStopReason == JobParameters.STOP_REASON_UNDEFINED
- || mTestJobStatus.stopReason == expectedStopReason);
- }
- });
- }
-
- private boolean waitUntilTrue(long timeout, Condition condition) throws InterruptedException {
- final long deadLine = SystemClock.uptimeMillis() + timeout;
- do {
- Thread.sleep(POLL_INTERVAL);
- } while (!condition.isTrue() && SystemClock.uptimeMillis() < deadLine);
- return condition.isTrue();
- }
-
- private static final class TestJobStatus {
- int jobId;
- int stopReason;
- boolean running;
-
- private void reset() {
- running = false;
- stopReason = jobId = 0;
- }
- }
-
- private interface Condition {
- boolean isTrue();
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 0f5fb91..d50affb 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -406,8 +406,8 @@
public void testPushDynamicShortcut() {
// Change the max number of shortcuts.
- mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5");
-
+ mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5,"
+ + ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=1");
setCaller(CALLING_PACKAGE_1, USER_0);
final ShortcutInfo s1 = makeShortcut("s1");
@@ -545,6 +545,57 @@
eq(CALLING_PACKAGE_1), eq("s9"), eq(USER_0));
}
+ public void testPushDynamicShortcut_CallsToUsageStatsManagerAreThrottled()
+ throws InterruptedException {
+ mService.updateConfigurationLocked(
+ ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=500");
+
+ // Verify calls to UsageStatsManagerInternal#reportShortcutUsage are throttled.
+ setCaller(CALLING_PACKAGE_1, USER_0);
+ {
+ final ShortcutInfo si = makeShortcut("s0");
+ mManager.pushDynamicShortcut(si);
+ }
+ verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
+ eq(CALLING_PACKAGE_1), eq("s0"), eq(USER_0));
+ Mockito.reset(mMockUsageStatsManagerInternal);
+ for (int i = 2; i <= 10; i++) {
+ final ShortcutInfo si = makeShortcut("s" + i);
+ mManager.pushDynamicShortcut(si);
+ }
+ verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage(
+ any(), any(), anyInt());
+
+ // Verify pkg2 isn't blocked by pkg1, but consecutive calls from pkg2 are throttled as well.
+ setCaller(CALLING_PACKAGE_2, USER_0);
+ {
+ final ShortcutInfo si = makeShortcut("s1");
+ mManager.pushDynamicShortcut(si);
+ }
+ verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
+ eq(CALLING_PACKAGE_2), eq("s1"), eq(USER_0));
+ Mockito.reset(mMockUsageStatsManagerInternal);
+ for (int i = 2; i <= 10; i++) {
+ final ShortcutInfo si = makeShortcut("s" + i);
+ mManager.pushDynamicShortcut(si);
+ }
+ verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage(
+ any(), any(), anyInt());
+
+ Mockito.reset(mMockUsageStatsManagerInternal);
+ // Let time passes which resets the throttle
+ Thread.sleep(505);
+ // Verify UsageStatsManagerInternal#reportShortcutUsed can be called again
+ setCaller(CALLING_PACKAGE_1, USER_0);
+ mManager.pushDynamicShortcut(makeShortcut("s10"));
+ setCaller(CALLING_PACKAGE_2, USER_0);
+ mManager.pushDynamicShortcut(makeShortcut("s10"));
+ verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
+ eq(CALLING_PACKAGE_1), any(), eq(USER_0));
+ verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
+ eq(CALLING_PACKAGE_2), any(), eq(USER_0));
+ }
+
public void testUnlimitedCalls() {
setCaller(CALLING_PACKAGE_1, USER_0);
diff --git a/services/tests/servicestests/test-apps/JobTestApp/Android.bp b/services/tests/servicestests/test-apps/JobTestApp/Android.bp
deleted file mode 100644
index 6458bcd..0000000
--- a/services/tests/servicestests/test-apps/JobTestApp/Android.bp
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (C) 2017 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 {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_test_helper_app {
- name: "JobTestApp",
-
- sdk_version: "current",
-
- srcs: ["**/*.java"],
-
- dex_preopt: {
- enabled: false,
- },
- optimize: {
- enabled: false,
- },
-}
diff --git a/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml
deleted file mode 100644
index ac35805..0000000
--- a/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.servicestests.apps.jobtestapp">
-
- <application>
- <service android:name=".TestJobService"
- android:permission="android.permission.BIND_JOB_SERVICE" />
- <activity android:name=".TestJobActivity"
- android:exported="true" />
- </application>
-
-</manifest>
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/JobTestApp/OWNERS b/services/tests/servicestests/test-apps/JobTestApp/OWNERS
deleted file mode 100644
index 6f207fb1..0000000
--- a/services/tests/servicestests/test-apps/JobTestApp/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /apex/jobscheduler/OWNERS
diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java
deleted file mode 100644
index 99eb196..0000000
--- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2017 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.servicestests.apps.jobtestapp;
-
-import android.app.Activity;
-import android.app.job.JobInfo;
-import android.app.job.JobScheduler;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-
-public class TestJobActivity extends Activity {
- private static final String TAG = TestJobActivity.class.getSimpleName();
- private static final String PACKAGE_NAME = "com.android.servicestests.apps.jobtestapp";
-
- public static final String EXTRA_JOB_ID_KEY = PACKAGE_NAME + ".extra.JOB_ID";
- public static final String ACTION_START_JOB = PACKAGE_NAME + ".action.START_JOB";
- public static final String ACTION_CANCEL_JOBS = PACKAGE_NAME + ".action.CANCEL_JOBS";
- public static final int JOB_INITIAL_BACKOFF = 10_000;
- public static final int JOB_MINIMUM_LATENCY = 5_000;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- ComponentName jobServiceComponent = new ComponentName(this, TestJobService.class);
- JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
- final Intent intent = getIntent();
- switch (intent.getAction()) {
- case ACTION_CANCEL_JOBS:
- jobScheduler.cancelAll();
- Log.d(TAG, "Cancelled all jobs for " + getPackageName());
- break;
- case ACTION_START_JOB:
- final int jobId = intent.getIntExtra(EXTRA_JOB_ID_KEY, hashCode());
- JobInfo.Builder jobBuilder = new JobInfo.Builder(jobId, jobServiceComponent)
- .setBackoffCriteria(JOB_INITIAL_BACKOFF, JobInfo.BACKOFF_POLICY_LINEAR)
- .setMinimumLatency(JOB_MINIMUM_LATENCY)
- .setOverrideDeadline(JOB_MINIMUM_LATENCY);
- final int result = jobScheduler.schedule(jobBuilder.build());
- if (result != JobScheduler.RESULT_SUCCESS) {
- Log.e(TAG, "Could not schedule job " + jobId);
- } else {
- Log.d(TAG, "Successfully scheduled job with id " + jobId);
- }
- break;
- default:
- Log.e(TAG, "Unknown action " + intent.getAction());
- }
- finish();
- }
-}
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java
deleted file mode 100644
index b8585f2..0000000
--- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2017 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.servicestests.apps.jobtestapp;
-
-import android.annotation.TargetApi;
-import android.app.job.JobParameters;
-import android.app.job.JobService;
-import android.content.Intent;
-import android.util.Log;
-
-@TargetApi(24)
-public class TestJobService extends JobService {
- private static final String TAG = TestJobService.class.getSimpleName();
- private static final String PACKAGE_NAME = "com.android.servicestests.apps.jobtestapp";
- public static final String ACTION_JOB_STARTED = PACKAGE_NAME + ".action.JOB_STARTED";
- public static final String ACTION_JOB_STOPPED = PACKAGE_NAME + ".action.JOB_STOPPED";
- public static final String JOB_PARAMS_EXTRA_KEY = PACKAGE_NAME + ".extra.JOB_PARAMETERS";
-
- @Override
- public boolean onStartJob(JobParameters params) {
- Log.i(TAG, "Test job executing: " + params.getJobId());
- Intent reportJobStartIntent = new Intent(ACTION_JOB_STARTED);
- reportJobStartIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- sendBroadcast(reportJobStartIntent);
- return true;
- }
-
- @Override
- public boolean onStopJob(JobParameters params) {
- Log.i(TAG, "Test job stopped executing: " + params.getJobId());
- Intent reportJobStopIntent = new Intent(ACTION_JOB_STOPPED);
- reportJobStopIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- sendBroadcast(reportJobStopIntent);
- // Deadline constraint is dropped on reschedule, so it's more reliable to use a new job.
- return false;
- }
-}
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 4e1c72a..2f29d10 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -47,6 +47,7 @@
"flag-junit",
"notification_flags_lib",
"platform-test-rules",
+ "SettingsLib",
],
libs: [
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index db46532..839cf7c 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -17,6 +17,9 @@
package com.android.server;
import static android.Manifest.permission.MODIFY_DAY_NIGHT_MODE;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_DAY;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
import static android.app.UiModeManager.MODE_NIGHT_AUTO;
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
@@ -32,6 +35,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.Assert.fail;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
@@ -65,6 +69,7 @@
import android.Manifest;
import android.app.Activity;
import android.app.AlarmManager;
+import android.app.Flags;
import android.app.IOnProjectionStateChangedListener;
import android.app.IUiModeManager;
import android.content.BroadcastReceiver;
@@ -84,6 +89,8 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.test.FakePermissionEnforcer;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.service.dreams.DreamManagerInternal;
import android.test.mock.MockContentResolver;
@@ -98,6 +105,7 @@
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -109,6 +117,7 @@
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.List;
+import java.util.Map;
import java.util.function.Consumer;
@RunWith(AndroidTestingRunner.class)
@@ -159,6 +168,11 @@
private TwilightListener mTwilightListener;
private FakePermissionEnforcer mPermissionEnforcer;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(
+ SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
+
+
@Before
public void setUp() {
// The AIDL stub will use PermissionEnforcer to check permission from the caller.
@@ -1437,6 +1451,51 @@
verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
}
+ private void testAttentionModeThemeOverlay(boolean modeNight) throws RemoteException {
+ //setup
+ if (modeNight) {
+ mService.setNightMode(MODE_NIGHT_YES);
+ assertTrue(mUiManagerService.getConfiguration().isNightModeActive());
+ } else {
+ mService.setNightMode(MODE_NIGHT_NO);
+ assertFalse(mUiManagerService.getConfiguration().isNightModeActive());
+ }
+
+ // attention modes with expected night modes
+ Map<Integer, Boolean> modes = Map.of(
+ MODE_ATTENTION_THEME_OVERLAY_OFF, modeNight,
+ MODE_ATTENTION_THEME_OVERLAY_DAY, false,
+ MODE_ATTENTION_THEME_OVERLAY_NIGHT, true
+ );
+
+ // test
+ for (int aMode : modes.keySet()) {
+ try {
+ mService.setAttentionModeThemeOverlay(aMode);
+
+ int appliedAMode = mService.getAttentionModeThemeOverlay();
+ boolean nMode = modes.get(aMode);
+
+ assertEquals(aMode, appliedAMode);
+ assertEquals(isNightModeActivated(), nMode);
+ } catch (RemoteException e) {
+ fail("Error communicating with server: " + e.getMessage());
+ }
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testAttentionModeThemeOverlay_nightModeDisabled() throws RemoteException {
+ testAttentionModeThemeOverlay(false);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testAttentionModeThemeOverlay_nightModeEnabled() throws RemoteException {
+ testAttentionModeThemeOverlay(true);
+ }
+
private void triggerDockIntent() {
final Intent dockedIntent =
new Intent(Intent.ACTION_DOCK_EVENT)
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
index 30843d2..3797dbb 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
@@ -16,7 +16,8 @@
package com.android.server.notification;
-import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP;
import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
@@ -121,28 +122,49 @@
verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
verify(mColorDisplayManager).setSaturationLevel(eq(0));
verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
- verify(mUiModeManager).setNightModeActivatedForCustomMode(
- eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+ verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
}
@Test
- public void apply_removesPreviouslyAppliedEffects() {
+ public void apply_removesEffects() {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder()
.setShouldSuppressAmbientDisplay(true)
.setShouldDimWallpaper(true)
+ .setShouldDisplayGrayscale(true)
+ .setShouldUseNightMode(true)
.build();
mApplier.apply(previousEffects, UPDATE_ORIGIN_USER);
verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
+ verify(mColorDisplayManager).setSaturationLevel(eq(0));
verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
+ verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build();
mApplier.apply(noEffects, UPDATE_ORIGIN_USER);
verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false));
+ verify(mColorDisplayManager).setSaturationLevel(eq(100));
verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f));
- verifyZeroInteractions(mColorDisplayManager, mUiModeManager);
+ verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_OFF));
+ }
+
+ @Test
+ public void apply_removesOnlyPreviouslyAppliedEffects() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+ ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder()
+ .setShouldSuppressAmbientDisplay(true)
+ .build();
+ mApplier.apply(previousEffects, UPDATE_ORIGIN_USER);
+ verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
+
+ ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build();
+ mApplier.apply(noEffects, UPDATE_ORIGIN_USER);
+
+ verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false));
+ verifyZeroInteractions(mColorDisplayManager, mWallpaperManager, mUiModeManager);
}
@Test
@@ -150,6 +172,7 @@
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mContext.addMockSystemService(ColorDisplayManager.class, null);
mContext.addMockSystemService(WallpaperManager.class, null);
+ mApplier = new DefaultDeviceEffectsApplier(mContext);
ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
.setShouldSuppressAmbientDisplay(true)
@@ -177,7 +200,7 @@
verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
verify(mPowerManager, never()).suppressAmbientDisplay(anyString(), anyBoolean());
- verify(mUiModeManager, never()).setNightModeActivatedForCustomMode(anyInt(), anyBoolean());
+ verify(mUiModeManager, never()).setAttentionModeThemeOverlay(anyInt());
}
@Test
@@ -223,8 +246,7 @@
screenOffReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
// So the effect is applied, and we stopped listening for this event.
- verify(mUiModeManager).setNightModeActivatedForCustomMode(
- eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+ verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
verify(mContext).unregisterReceiver(eq(screenOffReceiver));
}
@@ -239,8 +261,7 @@
origin.value());
// Effect was applied, and no broadcast receiver was registered.
- verify(mUiModeManager).setNightModeActivatedForCustomMode(
- eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+ verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
verify(mContext, never()).registerReceiver(any(), any(), anyInt());
}
@@ -256,8 +277,7 @@
origin.value());
// Effect was applied, and no broadcast receiver was registered.
- verify(mUiModeManager).setNightModeActivatedForCustomMode(
- eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+ verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
verify(mContext, never()).registerReceiver(any(), any(), anyInt());
}
diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java
index 336bfdd..a8fd6f2 100644
--- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java
+++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java
@@ -35,11 +35,13 @@
import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.Keep;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.RingBuffer;
import com.android.server.usage.BroadcastResponseStatsTracker.NotificationEventType;
+import java.util.function.IntFunction;
+import java.util.function.Supplier;
+
public class BroadcastResponseStatsLogger {
private static final int MAX_LOG_SIZE =
@@ -49,10 +51,10 @@
@GuardedBy("mLock")
private final LogBuffer mBroadcastEventsBuffer = new LogBuffer(
- BroadcastEvent.class, MAX_LOG_SIZE);
+ BroadcastEvent::new, BroadcastEvent[]::new, MAX_LOG_SIZE);
@GuardedBy("mLock")
private final LogBuffer mNotificationEventsBuffer = new LogBuffer(
- NotificationEvent.class, MAX_LOG_SIZE);
+ NotificationEvent::new, NotificationEvent[]::new, MAX_LOG_SIZE);
void logBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage,
UserHandle targetUser, long idForResponseEvent,
@@ -96,8 +98,8 @@
private static final class LogBuffer<T extends Data> extends RingBuffer<T> {
- LogBuffer(Class<T> classType, int capacity) {
- super(classType, capacity);
+ LogBuffer(Supplier<T> newItem, IntFunction<T[]> newBacking, int capacity) {
+ super(newItem, newBacking, capacity);
}
void logBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage,
@@ -179,8 +181,7 @@
}
}
- @Keep
- public static final class BroadcastEvent implements Data {
+ private static final class BroadcastEvent implements Data {
public int sourceUid;
public int targetUserId;
public int targetUidProcessState;
@@ -200,8 +201,7 @@
}
}
- @Keep
- public static final class NotificationEvent implements Data {
+ private static final class NotificationEvent implements Data {
public int type;
public String packageName;
public int userId;
@@ -218,7 +218,7 @@
}
}
- public interface Data {
+ private interface Data {
void reset();
}
}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java
index 1df7012..49ad461 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java
@@ -62,7 +62,8 @@
SubscriptionManager subscriptionManager,
TelephonyManager telephonyManager,
Callback callback) {
- mSubscriptionManager = Objects.requireNonNull(subscriptionManager);
+ mSubscriptionManager = Objects.requireNonNull(subscriptionManager)
+ .createForAllUserProfiles();
mTelephonyManager = Objects.requireNonNull(telephonyManager);
mCallback = Objects.requireNonNull(callback);
mSubscriptionManager.addOnSubscriptionsChangedListener(
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 57b13e9..e81f482 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1049,8 +1049,17 @@
public static final int PRESENTATION_UNAVAILABLE = 5;
+ /**
+ * Controls audio route for video calls.
+ * 0 - Use the default audio routing strategy.
+ * 1 - Disable the speaker. Route the audio to Headset or Bluetooth
+ * or Earpiece, based on the default audio routing strategy.
+ * @hide
+ */
+ public static final String PROPERTY_VIDEOCALL_AUDIO_OUTPUT = "persist.radio.call.audio.output";
+
/*
- * Values for the adb property "persist.radio.videocall.audio.output"
+ * Values for the adb property "persist.radio.call.audio.output"
*/
/** @hide */
public static final int AUDIO_OUTPUT_ENABLE_SPEAKER = 0;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 73c26a3..1badf67 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9694,6 +9694,27 @@
"remove_satellite_plmn_in_manual_network_scan_bool";
/**
+ * An integer key holds the time interval for refreshing or re-querying the satellite
+ * entitlement status from the entitlement server to ensure it is the latest.
+ *
+ * The default value is 30 days (1 month).
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT =
+ "satellite_entitlement_status_refresh_days_int";
+
+ /**
+ * This configuration enables device to query the entitlement server to get the satellite
+ * configuration.
+ * This will need agreement the carrier before enabling this flag.
+ *
+ * The default value is false.
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL =
+ "satellite_entitlement_supported_bool";
+
+ /**
* Indicating whether DUN APN should be disabled when the device is roaming. In that case,
* the default APN (i.e. internet) will be used for tethering.
*
@@ -10799,6 +10820,8 @@
sDefaults.putInt(KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT,
CellSignalStrengthLte.USE_RSRP);
sDefaults.putBoolean(KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, true);
+ sDefaults.putInt(KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT, 30);
+ sDefaults.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false);
sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 3b0397b..70047a6 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -928,10 +928,19 @@
@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1;
+ /**
+ * Satellite communication restricted by entitlement server. This can be triggered based on
+ * the EntitlementStatus value received from the entitlement server to enable or disable
+ * satellite.
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT = 2;
+
/** @hide */
@IntDef(prefix = "SATELLITE_COMMUNICATION_RESTRICTION_REASON_", value = {
SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER,
- SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION
+ SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION,
+ SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT
})
@Retention(RetentionPolicy.SOURCE)
public @interface SatelliteCommunicationRestrictionReason {}
diff --git a/test-base/Android.bp b/test-base/Android.bp
index 527159a..70a9540 100644
--- a/test-base/Android.bp
+++ b/test-base/Android.bp
@@ -120,12 +120,13 @@
path: "src",
}
-// Make the current.txt available for use by the cts/tests/signature tests.
+// Make the current.txt available for use by the cts/tests/signature and /vendor tests.
// ========================================================================
filegroup {
name: "android-test-base-current.txt",
visibility: [
"//cts/tests/signature/api",
+ "//vendor:__subpackages__",
],
srcs: [
"api/current.txt",
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 2ff7413..f37d2d1 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -77,12 +77,13 @@
auto_gen_config: true,
}
-// Make the current.txt available for use by the cts/tests/signature tests.
+// Make the current.txt available for use by the cts/tests/signature and /vendor tests.
// ========================================================================
filegroup {
name: "android-test-mock-current.txt",
visibility: [
"//cts/tests/signature/api",
+ "//vendor:__subpackages__",
],
srcs: [
"api/current.txt",
diff --git a/test-runner/Android.bp b/test-runner/Android.bp
index 13a5dac..21e09d3 100644
--- a/test-runner/Android.bp
+++ b/test-runner/Android.bp
@@ -79,12 +79,13 @@
],
}
-// Make the current.txt available for use by the cts/tests/signature tests.
+// Make the current.txt available for use by the cts/tests/signature and /vendor tests.
// ========================================================================
filegroup {
name: "android-test-runner-current.txt",
visibility: [
"//cts/tests/signature/api",
+ "//vendor:__subpackages__",
],
srcs: [
"api/current.txt",
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index f056110..1a82021 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -381,9 +381,9 @@
return true;
}
- bool FlattenTypeSpec(const ResourceTableTypeView& type,
- const std::vector<ResourceTableEntryView>& sorted_entries,
- BigBuffer* buffer) {
+ ResTable_typeSpec* FlattenTypeSpec(const ResourceTableTypeView& type,
+ const std::vector<ResourceTableEntryView>& sorted_entries,
+ BigBuffer* buffer) {
ChunkWriter type_spec_writer(buffer);
ResTable_typeSpec* spec_header =
type_spec_writer.StartChunk<ResTable_typeSpec>(RES_TABLE_TYPE_SPEC_TYPE);
@@ -391,7 +391,7 @@
if (sorted_entries.empty()) {
type_spec_writer.Finish();
- return true;
+ return spec_header;
}
// We can't just take the size of the vector. There may be holes in the
@@ -427,7 +427,7 @@
}
}
type_spec_writer.Finish();
- return true;
+ return spec_header;
}
bool FlattenTypes(BigBuffer* buffer) {
@@ -450,7 +450,8 @@
expected_type_id++;
type_pool_.MakeRef(type.named_type.to_string());
- if (!FlattenTypeSpec(type, type.entries, buffer)) {
+ const auto type_spec_header = FlattenTypeSpec(type, type.entries, buffer);
+ if (!type_spec_header) {
return false;
}
@@ -511,6 +512,10 @@
return false;
}
}
+
+ // And now we can update the type entries count in the typeSpec header.
+ type_spec_header->typesCount = android::util::HostToDevice16(uint16_t(std::min<uint32_t>(
+ config_to_entry_list_map.size(), std::numeric_limits<uint16_t>::max())));
}
return true;
}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java
new file mode 100644
index 0000000..379c4ae
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 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.okhttp.internalandroidapi;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.List;
+
+/**
+ * A domain name service that resolves IP addresses for host names.
+ * @hide
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface Dns {
+ /**
+ * Returns the IP addresses of {@code hostname}, in the order they should
+ * be attempted.
+ *
+ * @hide
+ */
+ List<InetAddress> lookup(String hostname) throws UnknownHostException;
+}