Merge "Remove startWaveformEffect APIs" into main
diff --git a/Android.bp b/Android.bp
index 48f0928..54cb268 100644
--- a/Android.bp
+++ b/Android.bp
@@ -107,7 +107,6 @@
":android.hardware.radio.data-V3-java-source",
":android.hardware.radio.network-V3-java-source",
":android.hardware.radio.voice-V3-java-source",
- ":android.hardware.security.keymint-V3-java-source",
":android.hardware.security.secureclock-V1-java-source",
":android.hardware.thermal-V3-java-source",
":android.hardware.tv.tuner-V3-java-source",
@@ -116,7 +115,6 @@
":android.security.legacykeystore-java-source",
":android.security.maintenance-java-source",
":android.security.metrics-java-source",
- ":android.system.keystore2-V4-java-source",
":android.hardware.cas-V1-java-source",
":credstore_aidl",
":dumpstate_aidl",
@@ -149,7 +147,16 @@
":statslog-framework-java-gen", // FrameworkStatsLog.java
":statslog-hwui-java-gen", // HwuiStatsLog.java
":audio_policy_configuration_V7_0",
- ],
+ ] + select(release_flag("RELEASE_ATTEST_MODULES"), {
+ true: [
+ ":android.hardware.security.keymint-V4-java-source",
+ ":android.system.keystore2-V5-java-source",
+ ],
+ default: [
+ ":android.hardware.security.keymint-V3-java-source",
+ ":android.system.keystore2-V4-java-source",
+ ],
+ }),
}
java_library {
@@ -398,6 +405,7 @@
"bouncycastle-repackaged-unbundled",
"com.android.sysprop.foldlockbehavior",
"com.android.sysprop.view",
+ "configinfra_framework_flags_java_lib",
"framework-internal-utils",
"dynamic_instrumentation_manager_aidl-java",
// If MimeMap ever becomes its own APEX, then this dependency would need to be removed
diff --git a/FF_LEADS_OWNERS b/FF_LEADS_OWNERS
new file mode 100644
index 0000000..a650c6b
--- /dev/null
+++ b/FF_LEADS_OWNERS
@@ -0,0 +1,10 @@
+bills@google.com
+carmenjackson@google.com
+nalini@google.com
+nosh@google.com
+olilan@google.com
+philipcuadra@google.com
+rajekumar@google.com
+shayba@google.com
+timmurray@google.com
+zezeozue@google.com
diff --git a/core/api/current.txt b/core/api/current.txt
index 203ef6a..19a32c1 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -13569,6 +13569,7 @@
field public static final String PROPERTY_MEDIA_CAPABILITIES = "android.media.PROPERTY_MEDIA_CAPABILITIES";
field public static final String PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES = "android.net.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES";
field public static final String PROPERTY_SPECIAL_USE_FGS_SUBTYPE = "android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE";
+ field @FlaggedApi("com.android.server.backup.enable_restricted_mode_changes") public static final String PROPERTY_USE_RESTRICTED_BACKUP_MODE = "android.app.backup.PROPERTY_USE_RESTRICTED_BACKUP_MODE";
field public static final int SIGNATURE_FIRST_NOT_SIGNED = -1; // 0xffffffff
field public static final int SIGNATURE_MATCH = 0; // 0x0
field public static final int SIGNATURE_NEITHER_SIGNED = 1; // 0x1
@@ -13805,6 +13806,7 @@
public final class SharedLibraryInfo implements android.os.Parcelable {
method public int describeContents();
+ method @FlaggedApi("android.content.pm.sdk_dependency_installer") @NonNull public java.util.List<java.lang.String> getCertDigests();
method @NonNull public android.content.pm.VersionedPackage getDeclaringPackage();
method @NonNull public java.util.List<android.content.pm.VersionedPackage> getDependentPackages();
method @IntRange(from=0xffffffff) public long getLongVersion();
@@ -17472,6 +17474,7 @@
method public void setFloatUniform(@NonNull String, @NonNull float[]);
method public void setInputColorFilter(@NonNull String, @NonNull android.graphics.ColorFilter);
method public void setInputShader(@NonNull String, @NonNull android.graphics.Shader);
+ method public void setInputXfermode(@NonNull String, @NonNull android.graphics.RuntimeXfermode);
method public void setIntUniform(@NonNull String, int);
method public void setIntUniform(@NonNull String, int, int);
method public void setIntUniform(@NonNull String, int, int, int);
@@ -17490,7 +17493,9 @@
method public void setFloatUniform(@NonNull String, float, float, float, float);
method public void setFloatUniform(@NonNull String, @NonNull float[]);
method public void setInputBuffer(@NonNull String, @NonNull android.graphics.BitmapShader);
+ method @FlaggedApi("com.android.graphics.hwui.flags.runtime_color_filters_blenders") public void setInputColorFilter(@NonNull String, @NonNull android.graphics.ColorFilter);
method public void setInputShader(@NonNull String, @NonNull android.graphics.Shader);
+ method @FlaggedApi("com.android.graphics.hwui.flags.runtime_color_filters_blenders") public void setInputXfermode(@NonNull String, @NonNull android.graphics.RuntimeXfermode);
method public void setIntUniform(@NonNull String, int);
method public void setIntUniform(@NonNull String, int, int);
method public void setIntUniform(@NonNull String, int, int, int);
@@ -17510,6 +17515,7 @@
method public void setFloatUniform(@NonNull String, @NonNull float[]);
method public void setInputColorFilter(@NonNull String, @NonNull android.graphics.ColorFilter);
method public void setInputShader(@NonNull String, @NonNull android.graphics.Shader);
+ method public void setInputXfermode(@NonNull String, @NonNull android.graphics.RuntimeXfermode);
method public void setIntUniform(@NonNull String, int);
method public void setIntUniform(@NonNull String, int, int);
method public void setIntUniform(@NonNull String, int, int, int);
@@ -40756,6 +40762,7 @@
method public int describeContents();
method @Deprecated @Nullable public android.os.Bundle getClientState();
method @Nullable public java.util.List<android.service.autofill.FillEventHistory.Event> getEvents();
+ method @FlaggedApi("android.service.autofill.autofill_w_metrics") public int getSessionId();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.FillEventHistory> CREATOR;
}
@@ -40765,10 +40772,12 @@
method @Nullable public android.os.Bundle getClientState();
method @Nullable public String getDatasetId();
method @NonNull public java.util.Map<android.view.autofill.AutofillId,android.service.autofill.FieldClassification> getFieldsClassification();
+ method @FlaggedApi("android.service.autofill.autofill_w_metrics") @Nullable public android.view.autofill.AutofillId getFocusedId();
method @NonNull public java.util.Set<java.lang.String> getIgnoredDatasetIds();
method @NonNull public java.util.Map<android.view.autofill.AutofillId,java.util.Set<java.lang.String>> getManuallyEnteredField();
method public int getNoSaveUiReason();
method @NonNull public java.util.Set<java.lang.String> getSelectedDatasetIds();
+ method @FlaggedApi("android.service.autofill.autofill_w_metrics") @NonNull public java.util.Set<java.lang.String> getShownDatasetIds();
method public int getType();
method public int getUiType();
field public static final int NO_SAVE_UI_REASON_DATASET_MATCH = 6; // 0x6
@@ -40777,6 +40786,7 @@
field public static final int NO_SAVE_UI_REASON_NONE = 0; // 0x0
field public static final int NO_SAVE_UI_REASON_NO_SAVE_INFO = 1; // 0x1
field public static final int NO_SAVE_UI_REASON_NO_VALUE_CHANGED = 4; // 0x4
+ field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final int NO_SAVE_UI_REASON_USING_CREDMAN = 7; // 0x7
field public static final int NO_SAVE_UI_REASON_WITH_DELAY_SAVE_FLAG = 2; // 0x2
field public static final int TYPE_AUTHENTICATION_SELECTED = 2; // 0x2
field public static final int TYPE_CONTEXT_COMMITTED = 4; // 0x4
@@ -40785,6 +40795,7 @@
field public static final int TYPE_DATASET_SELECTED = 0; // 0x0
field public static final int TYPE_SAVE_SHOWN = 3; // 0x3
field public static final int TYPE_VIEW_REQUESTED_AUTOFILL = 6; // 0x6
+ field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final int UI_TYPE_CREDMAN = 4; // 0x4
field public static final int UI_TYPE_DIALOG = 3; // 0x3
field public static final int UI_TYPE_INLINE = 2; // 0x2
field public static final int UI_TYPE_MENU = 1; // 0x1
@@ -42070,6 +42081,7 @@
public abstract class QuickAccessWalletService extends android.app.Service {
ctor public QuickAccessWalletService();
+ method @FlaggedApi("android.service.quickaccesswallet.launch_wallet_option_on_power_double_tap") @Nullable public android.app.PendingIntent getGestureTargetActivityPendingIntent();
method @Nullable public android.app.PendingIntent getTargetActivityPendingIntent();
method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
method public abstract void onWalletCardSelected(@NonNull android.service.quickaccesswallet.SelectWalletCardRequest);
@@ -49755,6 +49767,14 @@
method public abstract void updateMeasureState(@NonNull android.text.TextPaint);
}
+ @FlaggedApi("android.view.inputmethod.writing_tools") public final class NoWritingToolsSpan implements android.text.ParcelableSpan {
+ ctor public NoWritingToolsSpan();
+ method public int describeContents();
+ method public int getSpanTypeId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.text.style.NoWritingToolsSpan> CREATOR;
+ }
+
public interface ParagraphStyle {
}
@@ -54926,6 +54946,8 @@
method public abstract void setTransformation(android.graphics.Matrix);
method public abstract void setVisibility(int);
method public abstract void setWebDomain(@Nullable String);
+ field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final String EXTRA_VIRTUAL_STRUCTURE_TYPE = "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE";
+ field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final String EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER = "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER";
}
public abstract static class ViewStructure.HtmlInfo {
@@ -56648,6 +56670,11 @@
public final class AutofillId implements android.os.Parcelable {
method @NonNull public static android.view.autofill.AutofillId create(@NonNull android.view.View, int);
method public int describeContents();
+ method @FlaggedApi("android.service.autofill.autofill_w_metrics") public int getAutofillVirtualId();
+ method @FlaggedApi("android.service.autofill.autofill_w_metrics") public int getSessionId();
+ method @FlaggedApi("android.service.autofill.autofill_w_metrics") public int getViewId();
+ method @FlaggedApi("android.service.autofill.autofill_w_metrics") public boolean isInAutofillSession();
+ method @FlaggedApi("android.service.autofill.autofill_w_metrics") public boolean isVirtual();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.view.autofill.AutofillId> CREATOR;
}
diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt
index 4ada53e..ad5bd31 100644
--- a/core/api/lint-baseline.txt
+++ b/core/api/lint-baseline.txt
@@ -1,4 +1,10 @@
// Baseline format: 1.0
+ActionValue: android.view.ViewStructure#EXTRA_VIRTUAL_STRUCTURE_TYPE:
+ Inconsistent extra value; expected `android.view.extra.VIRTUAL_STRUCTURE_TYPE`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE`
+ActionValue: android.view.ViewStructure#EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER:
+ Inconsistent extra value; expected `android.view.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER`
+
+
BroadcastBehavior: android.app.AlarmManager#ACTION_NEXT_ALARM_CLOCK_CHANGED:
Field 'ACTION_NEXT_ALARM_CLOCK_CHANGED' is missing @BroadcastBehavior
BroadcastBehavior: android.app.AlarmManager#ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED:
@@ -1185,6 +1191,10 @@
New API must be flagged with @FlaggedApi: field android.R.dimen.system_corner_radius_xlarge
UnflaggedApi: android.R.dimen#system_corner_radius_xsmall:
New API must be flagged with @FlaggedApi: field android.R.dimen.system_corner_radius_xsmall
+UnflaggedApi: android.R.integer#status_bar_notification_info_maxnum:
+ Changes from not deprecated to deprecated must be flagged with @FlaggedApi: field android.R.integer.status_bar_notification_info_maxnum
+UnflaggedApi: android.R.string#status_bar_notification_info_overflow:
+ Changes from not deprecated to deprecated must be flagged with @FlaggedApi: field android.R.string.status_bar_notification_info_overflow
UnflaggedApi: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_INTERNAL_ERROR:
New API must be flagged with @FlaggedApi: field android.accessibilityservice.AccessibilityService.OVERLAY_RESULT_INTERNAL_ERROR
UnflaggedApi: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_INVALID:
@@ -1477,7 +1487,6 @@
New API must be flagged with @FlaggedApi: method android.graphics.text.PositionedGlyphs.getItalicOverride(int)
UnflaggedApi: android.graphics.text.PositionedGlyphs#getWeightOverride(int):
New API must be flagged with @FlaggedApi: method android.graphics.text.PositionedGlyphs.getWeightOverride(int)
-
UnflaggedApi: android.media.MediaRoute2Info#TYPE_REMOTE_CAR:
New API must be flagged with @FlaggedApi: field android.media.MediaRoute2Info.TYPE_REMOTE_CAR
UnflaggedApi: android.media.MediaRoute2Info#TYPE_REMOTE_COMPUTER:
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a46f872..9197a01 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -57,6 +57,7 @@
field @Deprecated public static final String BIND_CONNECTION_SERVICE = "android.permission.BIND_CONNECTION_SERVICE";
field public static final String BIND_CONTENT_CAPTURE_SERVICE = "android.permission.BIND_CONTENT_CAPTURE_SERVICE";
field public static final String BIND_CONTENT_SUGGESTIONS_SERVICE = "android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE";
+ field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final String BIND_DEPENDENCY_INSTALLER = "android.permission.BIND_DEPENDENCY_INSTALLER";
field public static final String BIND_DIRECTORY_SEARCH = "android.permission.BIND_DIRECTORY_SEARCH";
field public static final String BIND_DISPLAY_HASHING_SERVICE = "android.permission.BIND_DISPLAY_HASHING_SERVICE";
field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String BIND_DOMAIN_SELECTION_SERVICE = "android.permission.BIND_DOMAIN_SELECTION_SERVICE";
@@ -165,6 +166,7 @@
field public static final String HDMI_CEC = "android.permission.HDMI_CEC";
field @Deprecated public static final String HIDE_NON_SYSTEM_OVERLAY_WINDOWS = "android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS";
field public static final String INJECT_EVENTS = "android.permission.INJECT_EVENTS";
+ field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final String INSTALL_DEPENDENCY_SHARED_LIBRARIES = "android.permission.INSTALL_DEPENDENCY_SHARED_LIBRARIES";
field public static final String INSTALL_DPC_PACKAGES = "android.permission.INSTALL_DPC_PACKAGES";
field public static final String INSTALL_DYNAMIC_SYSTEM = "android.permission.INSTALL_DYNAMIC_SYSTEM";
field public static final String INSTALL_EXISTING_PACKAGES = "com.android.permission.INSTALL_EXISTING_PACKAGES";
@@ -531,6 +533,7 @@
field public static final int config_systemCallStreaming = 17039431; // 0x1040047
field public static final int config_systemCompanionDeviceProvider = 17039417; // 0x1040039
field public static final int config_systemContacts = 17039403; // 0x104002b
+ field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final int config_systemDependencyInstaller;
field public static final int config_systemFinancedDeviceController = 17039430; // 0x1040046
field public static final int config_systemGallery = 17039399; // 0x1040027
field public static final int config_systemNotificationIntelligence = 17039413; // 0x1040035
@@ -1339,8 +1342,10 @@
public class DevicePolicyManager {
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int checkProvisioningPrecondition(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void clearAuditLogEventCallback();
- method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
+ method @Deprecated @FlaggedApi("android.app.admin.flags.split_create_managed_profile_enabled") @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
+ method @FlaggedApi("android.app.admin.flags.split_create_managed_profile_enabled") @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
method @Nullable public android.content.Intent createProvisioningIntentFromNfcIntent(@NonNull android.content.Intent);
+ method @FlaggedApi("android.app.admin.flags.split_create_managed_profile_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void finalizeCreateManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams, @NonNull android.os.UserHandle) throws android.app.admin.ProvisioningException;
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void finalizeWorkProfileProvisioning(@NonNull android.os.UserHandle, @Nullable android.accounts.Account);
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public java.util.Set<java.lang.Integer> getApplicationExemptions(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
@@ -1935,6 +1940,7 @@
method public android.os.IBinder getBinder();
method public long getCurrentRestoreSet();
method public int getNextFullRestoreDataChunk(android.os.ParcelFileDescriptor);
+ method @FlaggedApi("com.android.server.backup.enable_restricted_mode_changes") @NonNull public java.util.List<java.lang.String> getPackagesThatShouldNotUseRestrictedMode(@NonNull java.util.List<java.lang.String>, int);
method public int getRestoreData(android.os.ParcelFileDescriptor);
method public int getTransportFlags();
method public int initializeDevice();
@@ -4588,6 +4594,24 @@
}
+package android.content.pm.dependencyinstaller {
+
+ @FlaggedApi("android.content.pm.sdk_dependency_installer") public final class DependencyInstallerCallback implements android.os.Parcelable {
+ method public int describeContents();
+ method public void onAllDependenciesResolved(@NonNull int[]);
+ method public void onFailureToResolveAllDependencies();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.dependencyinstaller.DependencyInstallerCallback> CREATOR;
+ }
+
+ @FlaggedApi("android.content.pm.sdk_dependency_installer") public abstract class DependencyInstallerService extends android.app.Service {
+ ctor public DependencyInstallerService();
+ method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
+ method public abstract void onDependenciesRequired(@NonNull java.util.List<android.content.pm.SharedLibraryInfo>, @NonNull android.content.pm.dependencyinstaller.DependencyInstallerCallback);
+ }
+
+}
+
package android.content.pm.dex {
public class ArtManager {
@@ -5220,6 +5244,19 @@
method @NonNull public android.hardware.contexthub.HubEndpointInfo getHubEndpointInfo();
}
+ @FlaggedApi("android.chre.flags.offload_api") public class HubEndpoint {
+ method @Nullable public android.hardware.contexthub.IHubEndpointLifecycleCallback getLifecycleCallback();
+ method @Nullable public String getTag();
+ }
+
+ public static final class HubEndpoint.Builder {
+ ctor public HubEndpoint.Builder(@NonNull android.content.Context);
+ method @NonNull public android.hardware.contexthub.HubEndpoint build();
+ method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull android.hardware.contexthub.IHubEndpointLifecycleCallback);
+ method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.IHubEndpointLifecycleCallback);
+ method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setTag(@NonNull String);
+ }
+
@FlaggedApi("android.chre.flags.offload_api") public final class HubEndpointInfo implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.hardware.contexthub.HubEndpointInfo.HubEndpointIdentifier getIdentifier();
@@ -5234,6 +5271,26 @@
method public long getHub();
}
+ @FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSession implements java.lang.AutoCloseable {
+ method public void close();
+ }
+
+ @FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSessionResult {
+ method @NonNull public static android.hardware.contexthub.HubEndpointSessionResult accept();
+ method @Nullable public String getReason();
+ method public boolean isAccepted();
+ method @NonNull public static android.hardware.contexthub.HubEndpointSessionResult reject(@NonNull String);
+ }
+
+ @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointLifecycleCallback {
+ method public void onSessionClosed(@NonNull android.hardware.contexthub.HubEndpointSession, int);
+ method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo);
+ method public void onSessionOpened(@NonNull android.hardware.contexthub.HubEndpointSession);
+ field public static final int REASON_CLOSE_ENDPOINT_SESSION_REQUESTED = 4; // 0x4
+ field public static final int REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED = 3; // 0x3
+ field public static final int REASON_UNSPECIFIED = 0; // 0x0
+ }
+
}
package android.hardware.devicestate {
@@ -5366,6 +5423,7 @@
method @Nullable @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public android.hardware.display.BrightnessConfiguration getDefaultBrightnessConfiguration();
method public android.util.Pair<float[],float[]> getMinimumBrightnessCurve();
method public android.graphics.Point getStableDisplaySize();
+ method @FlaggedApi("com.android.server.display.feature.flags.is_always_on_available_api") public boolean isAlwaysOnDisplayCurrentlyAvailable();
method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration);
method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfigurationForDisplay(@NonNull android.hardware.display.BrightnessConfiguration, @NonNull String);
method @Deprecated @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_SATURATION) public void setSaturationLevel(float);
@@ -6235,13 +6293,16 @@
method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.NanoAppInstanceInfo getNanoAppInstanceInfo(int);
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int loadNanoApp(int, @NonNull android.hardware.location.NanoApp);
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> loadNanoApp(@NonNull android.hardware.location.ContextHubInfo, @NonNull android.hardware.location.NanoAppBinary);
+ method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void openSession(@NonNull android.hardware.contexthub.HubEndpoint, @NonNull android.hardware.contexthub.HubEndpointInfo);
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.util.List<android.hardware.location.NanoAppState>> queryNanoApps(@NonNull android.hardware.location.ContextHubInfo);
method @Deprecated public int registerCallback(@NonNull android.hardware.location.ContextHubManager.Callback);
method @Deprecated public int registerCallback(android.hardware.location.ContextHubManager.Callback, android.os.Handler);
+ method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpoint(@NonNull android.hardware.contexthub.HubEndpoint);
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int sendMessage(int, int, @NonNull android.hardware.location.ContextHubMessage);
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int unloadNanoApp(int);
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> unloadNanoApp(@NonNull android.hardware.location.ContextHubInfo, long);
method @Deprecated public int unregisterCallback(@NonNull android.hardware.location.ContextHubManager.Callback);
+ method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void unregisterEndpoint(@NonNull android.hardware.contexthub.HubEndpoint);
field public static final int AUTHORIZATION_DENIED = 0; // 0x0
field public static final int AUTHORIZATION_DENIED_GRACE_PERIOD = 1; // 0x1
field public static final int AUTHORIZATION_GRANTED = 2; // 0x2
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 1192713..8fd2cd5 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2318,7 +2318,7 @@
public class SharedConnectivityManager {
method @Nullable public static android.net.wifi.sharedconnectivity.app.SharedConnectivityManager create(@NonNull android.content.Context, @NonNull String, @NonNull String);
- method @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api") @NonNull public android.content.BroadcastReceiver getBroadcastReceiver();
+ method @NonNull public android.content.BroadcastReceiver getBroadcastReceiver();
method @Nullable public android.content.ServiceConnection getServiceConnection();
method public void setService(@Nullable android.os.IInterface);
}
@@ -3240,6 +3240,7 @@
method @Nullable public android.content.Intent createWalletIntent();
method @Nullable public android.content.Intent createWalletSettingsIntent();
method public void disconnect();
+ method @FlaggedApi("android.service.quickaccesswallet.launch_wallet_option_on_power_double_tap") public void getGestureTargetActivityPendingIntent(@NonNull java.util.concurrent.Executor, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.GesturePendingIntentCallback);
method public void getWalletCards(@NonNull android.service.quickaccesswallet.GetWalletCardsRequest, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.OnWalletCardsRetrievedCallback);
method public void getWalletCards(@NonNull java.util.concurrent.Executor, @NonNull android.service.quickaccesswallet.GetWalletCardsRequest, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.OnWalletCardsRetrievedCallback);
method public void getWalletPendingIntent(@NonNull java.util.concurrent.Executor, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.WalletPendingIntentCallback);
@@ -3251,6 +3252,10 @@
method public void selectWalletCard(@NonNull android.service.quickaccesswallet.SelectWalletCardRequest);
}
+ @FlaggedApi("android.service.quickaccesswallet.launch_wallet_option_on_power_double_tap") public static interface QuickAccessWalletClient.GesturePendingIntentCallback {
+ method @FlaggedApi("android.service.quickaccesswallet.launch_wallet_option_on_power_double_tap") public void onGesturePendingIntentRetrieved(@Nullable android.app.PendingIntent);
+ }
+
public static interface QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
method public void onWalletCardRetrievalError(@NonNull android.service.quickaccesswallet.GetWalletCardsError);
method public void onWalletCardsRetrieved(@NonNull android.service.quickaccesswallet.GetWalletCardsResponse);
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 08bb082..c2fac70 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -1,4 +1,10 @@
// Baseline format: 1.0
+ActionValue: android.view.contentcapture.ViewNode.ViewStructureImpl#EXTRA_VIRTUAL_STRUCTURE_TYPE:
+ Inconsistent extra value; expected `android.view.contentcapture.extra.VIRTUAL_STRUCTURE_TYPE`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE`
+ActionValue: android.view.contentcapture.ViewNode.ViewStructureImpl#EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER:
+ Inconsistent extra value; expected `android.view.contentcapture.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER`
+
+
BannedThrow: android.os.vibrator.persistence.VibrationXmlSerializer#serialize(android.os.VibrationEffect, java.io.Writer):
Methods must not throw unchecked exceptions
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 6c03b32..ce0ec60 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1628,9 +1628,15 @@
public static final int OP_WRITE_SYSTEM_PREFERENCES =
AppOpEnums.APP_OP_WRITE_SYSTEM_PREFERENCES;
+ /** @hide Access to audio playback and control APIs. */
+ public static final int OP_CONTROL_AUDIO = AppOpEnums.APP_OP_CONTROL_AUDIO;
+
+ /** @hide Similar to {@link OP_CONTROL_AUDIO}, but doesn't require capabilities. */
+ public static final int OP_CONTROL_AUDIO_PARTIAL = AppOpEnums.APP_OP_CONTROL_AUDIO_PARTIAL;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 154;
+ public static final int _NUM_OP = 156;
/**
* All app ops represented as strings.
@@ -1788,6 +1794,8 @@
OPSTR_RANGING,
OPSTR_READ_OXYGEN_SATURATION,
OPSTR_WRITE_SYSTEM_PREFERENCES,
+ OPSTR_CONTROL_AUDIO,
+ OPSTR_CONTROL_AUDIO_PARTIAL,
})
public @interface AppOpString {}
@@ -2548,6 +2556,12 @@
/** @hide Access to system preferences write services */
public static final String OPSTR_WRITE_SYSTEM_PREFERENCES = "android:write_system_preferences";
+ /** @hide Access to audio playback and control APIs */
+ public static final String OPSTR_CONTROL_AUDIO = "android:control_audio";
+
+ /** @hide Access to a audio playback and control APIs without capability requirements */
+ public static final String OPSTR_CONTROL_AUDIO_PARTIAL = "android:control_audio_partial";
+
/** {@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} */
@@ -3157,6 +3171,10 @@
"WRITE_SYSTEM_PREFERENCES").setPermission(
com.android.settingslib.flags.Flags.writeSystemPreferencePermissionEnabled()
? Manifest.permission.WRITE_SYSTEM_PREFERENCES : null).build(),
+ new AppOpInfo.Builder(OP_CONTROL_AUDIO, OPSTR_CONTROL_AUDIO,
+ "CONTROL_AUDIO").setDefaultMode(AppOpsManager.MODE_FOREGROUND).build(),
+ new AppOpInfo.Builder(OP_CONTROL_AUDIO_PARTIAL, OPSTR_CONTROL_AUDIO_PARTIAL,
+ "CONTROL_AUDIO_PARTIAL").setDefaultMode(AppOpsManager.MODE_FOREGROUND).build(),
};
// The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index e2479169..7e0a9b6 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -16,6 +16,7 @@
package android.app;
+import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM;
import static android.app.PropertyInvalidatedCache.createSystemCacheKey;
import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_NOT_COLORED;
@@ -783,43 +784,24 @@
}
/**
+ * The API and cache name for hasSystemFeature.
+ */
+ private static final String HAS_SYSTEM_FEATURE_API = "has_system_feature";
+
+ /**
* Identifies a single hasSystemFeature query.
*/
- @Immutable
- private static final class HasSystemFeatureQuery {
- public final String name;
- public final int version;
- public HasSystemFeatureQuery(String n, int v) {
- name = n;
- version = v;
- }
- @Override
- public String toString() {
- return String.format("HasSystemFeatureQuery(name=\"%s\", version=%d)",
- name, version);
- }
- @Override
- public boolean equals(@Nullable Object o) {
- if (o instanceof HasSystemFeatureQuery) {
- HasSystemFeatureQuery r = (HasSystemFeatureQuery) o;
- return Objects.equals(name, r.name) && version == r.version;
- } else {
- return false;
- }
- }
- @Override
- public int hashCode() {
- return Objects.hashCode(name) * 13 + version;
- }
- }
+ private record HasSystemFeatureQuery(String name, int version) {}
// Make this cache relatively large. There are many system features and
// none are ever invalidated. MPTS tests suggests that the cache should
// hold at least 150 entries.
private final static PropertyInvalidatedCache<HasSystemFeatureQuery, Boolean>
- mHasSystemFeatureCache =
- new PropertyInvalidatedCache<HasSystemFeatureQuery, Boolean>(
- 256, createSystemCacheKey("has_system_feature")) {
+ mHasSystemFeatureCache = new PropertyInvalidatedCache<>(
+ new PropertyInvalidatedCache.Args(MODULE_SYSTEM)
+ .api(HAS_SYSTEM_FEATURE_API).maxEntries(256).isolateUids(false),
+ HAS_SYSTEM_FEATURE_API, null) {
+
@Override
public Boolean recompute(HasSystemFeatureQuery query) {
try {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 34a3ad1..a8412fa 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -358,7 +358,7 @@
@UnsupportedAppUsage
void resumeAppSwitches();
boolean bindBackupAgent(in String packageName, int backupRestoreMode, int targetUserId,
- int backupDestination);
+ int backupDestination, boolean useRestrictedMode);
void backupAgentCreated(in String packageName, in IBinder agent, int userId);
void unbindBackupAgent(in ApplicationInfo appInfo);
int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 1dc7742..675152f 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -1947,10 +1947,12 @@
}
// Return true if this cache has had any activity. If the hits, misses, and skips are all
- // zero then the client never tried to use the cache.
- private boolean isActive() {
+ // zero then the client never tried to use the cache. If invalidations and corks are also
+ // zero then the server never tried to use the cache.
+ private boolean isActive(NonceHandler.Stats stats) {
synchronized (mLock) {
- return mHits + mMisses + getSkipsLocked() > 0;
+ return mHits + mMisses + getSkipsLocked()
+ + stats.invalidated + stats.corkedInvalidates > 0;
}
}
@@ -1968,15 +1970,15 @@
NonceHandler.Stats stats = mNonce.getStats();
synchronized (mLock) {
- if (brief && !isActive()) {
+ if (brief && !isActive(stats)) {
return;
}
pw.println(formatSimple(" Cache Name: %s", cacheName()));
pw.println(formatSimple(" Property: %s", mPropertyName));
pw.println(formatSimple(
- " Hits: %d, Misses: %d, Skips: %d, Clears: %d, Uids: %d",
- mHits, mMisses, getSkipsLocked(), mClears, mCache.size()));
+ " Hits: %d, Misses: %d, Skips: %d, Clears: %d",
+ mHits, mMisses, getSkipsLocked(), mClears));
// Print all the skip reasons.
pw.format(" Skip-%s: %d", sNonceName[0], mSkips[0]);
@@ -1986,7 +1988,7 @@
pw.println();
pw.println(formatSimple(
- " Nonce: 0x%016x, Invalidates: %d, CorkedInvalidates: %d",
+ " Nonce: 0x%016x, Invalidates: %d, Corked: %d",
mLastSeenNonce, stats.invalidated, stats.corkedInvalidates));
pw.println(formatSimple(
" Current Size: %d, Max Size: %d, HW Mark: %d, Overflows: %d",
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index a063917..53a7dad 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -17,6 +17,7 @@
package android.app;
import static android.app.appfunctions.flags.Flags.enableAppFunctionManager;
+import static android.provider.flags.Flags.stageFlagsForBuild;
import static android.server.Flags.removeGameManagerServiceFromWear;
import android.accounts.AccountManager;
@@ -216,6 +217,7 @@
import android.os.UserManager;
import android.os.Vibrator;
import android.os.VibratorManager;
+import android.os.flagging.ConfigInfrastructureFrameworkInitializer;
import android.os.health.SystemHealthManager;
import android.os.image.DynamicSystemManager;
import android.os.image.IDynamicSystemService;
@@ -1837,6 +1839,10 @@
VirtualizationFrameworkInitializer.registerServiceWrappers();
ConnectivityFrameworkInitializerBaklava.registerServiceWrappers();
+ if (stageFlagsForBuild()) {
+ ConfigInfrastructureFrameworkInitializer.registerServiceWrappers();
+ }
+
if (com.android.server.telecom.flags.Flags.telecomMainlineBlockedNumbersManager()) {
ProviderFrameworkInitializer.registerServiceWrappers();
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 4e68b5a..e766ae2 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -16,6 +16,7 @@
package android.app.admin;
+import static android.app.admin.flags.Flags.FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.LOCK_DEVICE;
@@ -17196,11 +17197,14 @@
* @throws SecurityException if the caller does not hold
* {@link android.Manifest.permission#MANAGE_PROFILE_AND_DEVICE_OWNERS}.
* @throws ProvisioningException if an error occurred during provisioning.
+ * @deprecated Use {@link #createManagedProfile} and {@link #finalizeCreateManagedProfile}
* @hide
*/
@Nullable
@SystemApi
+ @Deprecated
@RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @FlaggedApi(FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED)
public UserHandle createAndProvisionManagedProfile(
@NonNull ManagedProfileProvisioningParams provisioningParams)
throws ProvisioningException {
@@ -17218,6 +17222,69 @@
}
/**
+ * Creates a managed profile and sets the
+ * {@link ManagedProfileProvisioningParams#getProfileAdminComponentName()} as the profile
+ * owner. The method {@link #finalizeCreateManagedProfile} must be called after to finalize the
+ * creation of the managed profile.
+ *
+ * <p>The method {@link #checkProvisioningPrecondition} must return {@link #STATUS_OK}
+ * before calling this method. If it doesn't, a ProvisioningException will be thrown.
+ *
+ * @param provisioningParams Params required to provision a managed profile,
+ * see {@link ManagedProfileProvisioningParams}.
+ * @return The {@link UserHandle} of the created profile or {@code null} if the service is
+ * not available.
+ * @throws ProvisioningException if an error occurred during provisioning.
+ * @hide
+ */
+ @Nullable
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @FlaggedApi(FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED)
+ public UserHandle createManagedProfile(
+ @NonNull ManagedProfileProvisioningParams provisioningParams)
+ throws ProvisioningException {
+ if (mService == null) {
+ return null;
+ }
+ try {
+ return mService.createManagedProfile(provisioningParams, mContext.getPackageName());
+ } catch (ServiceSpecificException e) {
+ throw new ProvisioningException(e, e.errorCode, getErrorMessage(e));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Finalizes the creation of a managed profile by informing the necessary components that
+ * the managed profile is ready.
+ *
+ * @param provisioningParams Params required to provision a managed profile,
+ * see {@link ManagedProfileProvisioningParams}.
+ * @param managedProfileUser The recently created managed profile.
+ * @throws ProvisioningException if an error occurred during provisioning.
+ * @hide
+ */
+ @SuppressLint("UserHandle")
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ @FlaggedApi(FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED)
+ public void finalizeCreateManagedProfile(
+ @NonNull ManagedProfileProvisioningParams provisioningParams,
+ @NonNull UserHandle managedProfileUser)
+ throws ProvisioningException {
+ if (mService == null) {
+ return;
+ }
+ try {
+ mService.finalizeCreateManagedProfile(provisioningParams, managedProfileUser);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Removes a manged profile from the device only when called from a managed profile's context
*
* @param user UserHandle of the profile to be removed
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index fa984af..d048b53 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -570,6 +570,8 @@
void setOrganizationIdForUser(in String callerPackage, in String enterpriseId, int userId);
UserHandle createAndProvisionManagedProfile(in ManagedProfileProvisioningParams provisioningParams, in String callerPackage);
+ UserHandle createManagedProfile(in ManagedProfileProvisioningParams provisioningParams, in String callerPackage);
+ void finalizeCreateManagedProfile(in ManagedProfileProvisioningParams provisioningParams, in UserHandle managedProfileUser);
void provisionFullyManagedDevice(in FullyManagedDeviceProvisioningParams provisioningParams, in String callerPackage);
void finalizeWorkProfileProvisioning(in UserHandle managedProfileUser, in Account migratedAccount);
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 0088925..581efa5 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -382,7 +382,7 @@
is_exported: true
namespace: "enterprise"
description: "Allows DPMS to enable or disable SupervisionService based on whether the device is being managed by the supervision role holder."
- bug: "376213673"
+ bug: "358134581"
}
flag {
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index dcac59c..5004c02 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -16,6 +16,8 @@
package android.app.backup;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.Intent;
@@ -27,6 +29,7 @@
import com.android.internal.backup.IBackupTransport;
import com.android.internal.backup.ITransportStatusCallback;
import com.android.internal.infra.AndroidFuture;
+import com.android.server.backup.Flags;
import java.util.Arrays;
import java.util.List;
@@ -671,6 +674,22 @@
}
/**
+ * Ask the transport whether packages that are about to be backed up or restored should not be
+ * put into a restricted mode by the framework and started normally instead.
+ *
+ * @param operationType 0 for backup, 1 for restore.
+ * @return a subset of the {@code packageNames} passed in, indicating
+ * which packages should NOT be put into restricted mode for the given operation type.
+ */
+ @NonNull
+ @FlaggedApi(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public List<String> getPackagesThatShouldNotUseRestrictedMode(
+ @NonNull List<String> packageNames,
+ @BackupAnnotations.OperationType int operationType) {
+ return List.of();
+ }
+
+ /**
* Bridge between the actual IBackupTransport implementation and the stable API. If the
* binder interface needs to change, we use this layer to translate so that we can
* (if appropriate) decouple those framework-side changes from the BackupTransport
@@ -977,5 +996,19 @@
resultFuture.cancel(/* mayInterruptIfRunning */ true);
}
}
+
+ @Override
+ @FlaggedApi(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public void getPackagesThatShouldNotUseRestrictedMode(List<String> packageNames,
+ int operationType, AndroidFuture<List<String>> resultFuture) {
+ try {
+ List<String> result =
+ BackupTransport.this.getPackagesThatShouldNotUseRestrictedMode(packageNames,
+ operationType);
+ resultFuture.complete(result);
+ } catch (RuntimeException e) {
+ resultFuture.cancel(/* mayInterruptIfRunning */ true);
+ }
+ }
}
}
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index c47fe23..de01280 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -148,6 +148,14 @@
flag {
namespace: "virtual_devices"
+ name: "notifications_for_device_streaming"
+ description: "Add notifications permissions to device streaming role"
+ bug: "375240276"
+ is_exported: true
+}
+
+flag {
+ namespace: "virtual_devices"
name: "default_device_camera_access_policy"
description: "API for default device camera access policy"
bug: "371173368"
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 13b13b9..37295ac 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -193,6 +193,42 @@
"android.net.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES";
/**
+ * <application> level {@link android.content.pm.PackageManager.Property} tag
+ * specifying whether the app should be put into the "restricted" backup mode when it's started
+ * for backup and restore operations.
+ *
+ * <p> See <a
+ * href="https://developer.android.com/identity/data/autobackup#ImplementingBackupAgent"> for
+ * information about restricted mode</a>.
+ *
+ * <p> Starting with Android 16 apps may not be started in restricted mode based on this
+ * property.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <application>
+ * <property
+ * android:name="android.app.backup.PROPERTY_USE_RESTRICTED_BACKUP_MODE"
+ * android:value="true|false"/>
+ * </application>
+ * </pre>
+ *
+ * <p>If this property is set, the operating system will respect it for now (see Note below).
+ * If it's not set, the behavior depends on the SDK level that the app is targeting. For apps
+ * targeting SDK level {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} or lower, the
+ * property defaults to {@code true}. For apps targeting SDK level
+ * {@link android.os.Build.VERSION_CODES#BAKLAVA} or higher, the operating system will make a
+ * decision dynamically.
+ *
+ * <p>Note: It's not recommended to set this property to {@code true} unless absolutely
+ * necessary. In a future Android version, this property may be deprecated in favor of removing
+ * restricted mode completely.
+ */
+ @FlaggedApi(com.android.server.backup.Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public static final String PROPERTY_USE_RESTRICTED_BACKUP_MODE =
+ "android.app.backup.PROPERTY_USE_RESTRICTED_BACKUP_MODE";
+
+ /**
* Application level property that an app can specify to opt-out from having private data
* directories both on the internal and external storages.
*
diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java
index f7191e6..5dfec98 100644
--- a/core/java/android/content/pm/SharedLibraryInfo.java
+++ b/core/java/android/content/pm/SharedLibraryInfo.java
@@ -105,6 +105,8 @@
private final List<VersionedPackage> mOptionalDependentPackages;
private List<SharedLibraryInfo> mDependencies;
+ private final List<String> mCertDigests;
+
/**
* Creates a new instance.
*
@@ -134,6 +136,7 @@
mDependencies = dependencies;
mIsNative = isNative;
mOptionalDependentPackages = null;
+ mCertDigests = null;
}
/**
@@ -165,6 +168,7 @@
mDeclaringPackage = declaringPackage;
mDependencies = dependencies;
mIsNative = isNative;
+ mCertDigests = null;
var allDependents = allDependentPackages.first;
var usesLibOptional = allDependentPackages.second;
@@ -206,6 +210,7 @@
mIsNative = parcel.readBoolean();
mOptionalDependentPackages = parcel.readParcelableList(new ArrayList<>(),
VersionedPackage.class.getClassLoader(), VersionedPackage.class);
+ mCertDigests = parcel.createStringArrayList();
}
/**
@@ -214,6 +219,7 @@
* @param versionMajor
*/
public SharedLibraryInfo(String name, long versionMajor, int type) {
+ //TODO: change to this(name, versionMajor, type, /* certDigest= */null); after flag removal
mPath = null;
mPackageName = null;
mName = name;
@@ -224,6 +230,29 @@
mDependencies = null;
mIsNative = false;
mOptionalDependentPackages = null;
+ mCertDigests = null;
+ }
+
+ /**
+ * @hide
+ * @param name The lib name.
+ * @param versionMajor The lib major version.
+ * @param type The type of shared library.
+ * @param certDigests The list of certificate digests for this shared library.
+ */
+ @FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+ public SharedLibraryInfo(String name, long versionMajor, int type, List<String> certDigests) {
+ mPath = null;
+ mPackageName = null;
+ mName = name;
+ mVersion = versionMajor;
+ mType = type;
+ mDeclaringPackage = null;
+ mDependentPackages = null;
+ mDependencies = null;
+ mIsNative = false;
+ mOptionalDependentPackages = null;
+ mCertDigests = certDigests;
}
/**
@@ -433,6 +462,19 @@
return mOptionalDependentPackages;
}
+ /**
+ * Gets the list of certificate digests for the shared library.
+ *
+ * @return The list of certificate digests
+ */
+ @FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+ public @NonNull List<String> getCertDigests() {
+ if (mCertDigests == null) {
+ return Collections.emptyList();
+ }
+ return mCertDigests;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -463,6 +505,7 @@
parcel.writeTypedList(mDependencies);
parcel.writeBoolean(mIsNative);
parcel.writeParcelableList(mOptionalDependentPackages, flags);
+ parcel.writeStringList(mCertDigests);
}
private static String typeToString(int type) {
diff --git a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.aidl b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.aidl
new file mode 100644
index 0000000..06fcabc
--- /dev/null
+++ b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.content.pm.dependencyinstaller;
+
+parcelable DependencyInstallerCallback;
\ No newline at end of file
diff --git a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java
new file mode 100644
index 0000000..ba089f7
--- /dev/null
+++ b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java
@@ -0,0 +1,100 @@
+/**
+ * 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.content.pm.dependencyinstaller;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.pm.Flags;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+/**
+ * Callbacks for {@link DependencyInstallerService}. The implementation of
+ * DependencyInstallerService uses this interface to indicate completion of the session creation
+ * request given by the system server.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+public final class DependencyInstallerCallback implements Parcelable {
+ private final IBinder mBinder;
+ private final IDependencyInstallerCallback mCallback;
+
+ /** @hide */
+ public DependencyInstallerCallback(IBinder binder) {
+ mBinder = binder;
+ mCallback = IDependencyInstallerCallback.Stub.asInterface(binder);
+ }
+
+ private DependencyInstallerCallback(Parcel in) {
+ mBinder = in.readStrongBinder();
+ mCallback = IDependencyInstallerCallback.Stub.asInterface(mBinder);
+ }
+
+ /**
+ * Callback to indicate that all the requested dependencies have been resolved and their
+ * sessions created. See {@link DependencyInstallerService#onDependenciesRequired}.
+ *
+ * @param sessionIds the install session IDs for all requested dependencies
+ */
+ public void onAllDependenciesResolved(@NonNull int[] sessionIds) {
+ try {
+ mCallback.onAllDependenciesResolved(sessionIds);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Callback to indicate that at least one of the required dependencies could not be resolved
+ * and any associated sessions have been abandoned.
+ */
+ public void onFailureToResolveAllDependencies() {
+ try {
+ mCallback.onFailureToResolveAllDependencies();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeStrongBinder(mBinder);
+ }
+
+ public static final @NonNull Creator<DependencyInstallerCallback> CREATOR =
+ new Creator<>() {
+ @Override
+ public DependencyInstallerCallback createFromParcel(Parcel in) {
+ return new DependencyInstallerCallback(in);
+ }
+
+ @Override
+ public DependencyInstallerCallback[] newArray(int size) {
+ return new DependencyInstallerCallback[size];
+ }
+ };
+}
diff --git a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerService.java b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerService.java
new file mode 100644
index 0000000..1186415
--- /dev/null
+++ b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerService.java
@@ -0,0 +1,83 @@
+/**
+ * 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.content.pm.dependencyinstaller;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.Flags;
+import android.content.pm.SharedLibraryInfo;
+import android.os.IBinder;
+
+import java.util.List;
+
+/**
+ * Service that needs to be implemented by the holder of the DependencyInstaller role. This service
+ * will be invoked by the system during application installations if it depends on
+ * {@link android.content.pm.SharedLibraryInfo#TYPE_STATIC} or
+ * {@link android.content.pm.SharedLibraryInfo#TYPE_SDK_PACKAGE} and those dependencies aren't
+ * already installed.
+ * <p>
+ * Below is an example manifest registration for a {@code DependencyInstallerService}.
+ * <pre>
+ * {@code
+ * <service android:name=".ExampleDependencyInstallerService"
+ * android:permission="android.permission.BIND_DEPENDENCY_INSTALLER" >
+ * ...
+ * <intent-filter>
+ * <action android:name="android.content.pm.action.INSTALL_DEPENDENCY" />
+ * </intent-filter>
+ * </service>
+ * }
+ * </pre>
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+public abstract class DependencyInstallerService extends Service {
+
+ private IDependencyInstallerService mBinder;
+
+ @Override
+ public final @NonNull IBinder onBind(@Nullable Intent intent) {
+ if (mBinder == null) {
+ mBinder = new IDependencyInstallerService.Stub() {
+ @Override
+ public void onDependenciesRequired(List<SharedLibraryInfo> neededLibraries,
+ DependencyInstallerCallback callback) {
+ DependencyInstallerService.this.onDependenciesRequired(neededLibraries,
+ callback);
+ }
+ };
+ }
+ return mBinder.asBinder();
+ }
+
+ /**
+ * Notify the holder of the DependencyInstaller role of the missing dependencies required for
+ * the completion of an active install session.
+ *
+ * @param neededLibraries the list of shared library dependencies needed to be obtained and
+ * installed.
+ */
+ public abstract void onDependenciesRequired(@NonNull List<SharedLibraryInfo> neededLibraries,
+ @NonNull DependencyInstallerCallback callback);
+}
diff --git a/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl
new file mode 100644
index 0000000..92d1d9e
--- /dev/null
+++ b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl
@@ -0,0 +1,41 @@
+/**
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.dependencyinstaller;
+
+import java.util.List;
+
+/**
+* Callbacks for Dependency Installer. The app side invokes on this interface to indicate
+* completion of the async dependency install request given by the system server.
+*
+* {@hide}
+*/
+oneway interface IDependencyInstallerCallback {
+ /**
+ * Callback to indicate that all the requested dependencies have been resolved and have been
+ * committed for installation. See {@link DependencyInstallerService#onDependenciesRequired}.
+ *
+ * @param sessionIds the install session IDs for all requested dependencies
+ */
+ void onAllDependenciesResolved(in int[] sessionIds);
+
+ /**
+ * Callback to indicate that at least one of the required dependencies could not be resolved
+ * and any associated sessions have been abandoned.
+ */
+ void onFailureToResolveAllDependencies();
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerService.aidl b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerService.aidl
new file mode 100644
index 0000000..94f5bf4
--- /dev/null
+++ b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerService.aidl
@@ -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 android.content.pm.dependencyinstaller;
+
+import android.content.pm.dependencyinstaller.DependencyInstallerCallback;
+import android.content.pm.SharedLibraryInfo;
+import java.util.List;
+
+/**
+* Interface used to communicate with the application code that holds the Dependency Installer role.
+* {@hide}
+*/
+oneway interface IDependencyInstallerService {
+ /**
+ * Notify dependency installer of the required dependencies to complete the current install
+ * session.
+ *
+ * @param neededLibraries the list of shared library dependencies needed to be obtained and
+ * installed.
+ */
+ void onDependenciesRequired(in List<SharedLibraryInfo> neededLibraries,
+ in DependencyInstallerCallback callback);
+ }
\ No newline at end of file
diff --git a/core/java/android/hardware/contexthub/HubEndpoint.java b/core/java/android/hardware/contexthub/HubEndpoint.java
new file mode 100644
index 0000000..99b05da
--- /dev/null
+++ b/core/java/android/hardware/contexthub/HubEndpoint.java
@@ -0,0 +1,406 @@
+/*
+ * 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.hardware.contexthub;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.chre.flags.Flags;
+import android.content.Context;
+import android.hardware.location.IContextHubService;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.SparseArray;
+
+import androidx.annotation.GuardedBy;
+
+import java.util.concurrent.Executor;
+
+/**
+ * An object representing an endpoint exposed to ContextHub and VendorHub. The object encapsulates
+ * the lifecycle and message callbacks for an endpoint.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OFFLOAD_API)
+public class HubEndpoint {
+ private static final String TAG = "HubEndpoint";
+
+ private final Object mLock = new Object();
+ private final HubEndpointInfo mPendingHubEndpointInfo;
+ @Nullable private final IHubEndpointLifecycleCallback mLifecycleCallback;
+ @NonNull private final Executor mLifecycleCallbackExecutor;
+
+ @GuardedBy("mLock")
+ private final SparseArray<HubEndpointSession> mActiveSessions = new SparseArray<>();
+
+ private final IContextHubEndpointCallback mServiceCallback =
+ new IContextHubEndpointCallback.Stub() {
+ @Override
+ public void onSessionOpenRequest(int sessionId, HubEndpointInfo initiator)
+ throws RemoteException {
+ HubEndpointSession activeSession;
+ synchronized (mLock) {
+ activeSession = mActiveSessions.get(sessionId);
+ // TODO(b/378974199): Consider refactor these assertions
+ if (activeSession != null) {
+ Log.i(
+ TAG,
+ "onSessionOpenComplete: session already exists, id="
+ + sessionId);
+ return;
+ }
+ }
+
+ if (mLifecycleCallback != null) {
+ mLifecycleCallbackExecutor.execute(
+ () ->
+ processSessionOpenRequestResult(
+ sessionId,
+ initiator,
+ mLifecycleCallback.onSessionOpenRequest(
+ initiator)));
+ }
+ }
+
+ private void processSessionOpenRequestResult(
+ int sessionId, HubEndpointInfo initiator, HubEndpointSessionResult result) {
+ if (result == null) {
+ throw new IllegalArgumentException(
+ "HubEndpointSessionResult shouldn't be null.");
+ }
+
+ if (result.isAccepted()) {
+ acceptSession(sessionId, initiator);
+ } else {
+ Log.i(
+ TAG,
+ "Session "
+ + sessionId
+ + " from "
+ + initiator
+ + " was rejected, reason="
+ + result.getReason());
+ rejectSession(sessionId);
+ }
+ }
+
+ private void acceptSession(int sessionId, HubEndpointInfo initiator) {
+ if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
+ // No longer registered?
+ return;
+ }
+
+ // Retrieve the active session
+ HubEndpointSession activeSession;
+ synchronized (mLock) {
+ activeSession = mActiveSessions.get(sessionId);
+ // TODO(b/378974199): Consider refactor these assertions
+ if (activeSession != null) {
+ Log.e(
+ TAG,
+ "onSessionOpenRequestResult: session already exists, id="
+ + sessionId);
+ return;
+ }
+
+ activeSession =
+ new HubEndpointSession(
+ sessionId,
+ HubEndpoint.this,
+ mAssignedHubEndpointInfo,
+ initiator);
+ try {
+ // oneway call to notify system service that the request is completed
+ mServiceToken.openSessionRequestComplete(sessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "onSessionOpenRequestResult: ", e);
+ return;
+ }
+
+ mActiveSessions.put(sessionId, activeSession);
+ }
+
+ // Execute the callback
+ activeSession.setOpened();
+ if (mLifecycleCallback != null) {
+ final HubEndpointSession finalActiveSession = activeSession;
+ mLifecycleCallbackExecutor.execute(
+ () -> mLifecycleCallback.onSessionOpened(finalActiveSession));
+ }
+ }
+
+ private void rejectSession(int sessionId) {
+ if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
+ // No longer registered?
+ return;
+ }
+
+ try {
+ mServiceToken.closeSession(
+ sessionId,
+ IHubEndpointLifecycleCallback
+ .REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void onSessionOpenComplete(int sessionId) throws RemoteException {
+ final HubEndpointSession activeSession;
+
+ // Retrieve the active session
+ synchronized (mLock) {
+ activeSession = mActiveSessions.get(sessionId);
+ }
+ // TODO(b/378974199): Consider refactor these assertions
+ if (activeSession == null) {
+ Log.i(
+ TAG,
+ "onSessionOpenComplete: no pending session open request? id="
+ + sessionId);
+ return;
+ }
+
+ // Execute the callback
+ activeSession.setOpened();
+ if (mLifecycleCallback != null) {
+ mLifecycleCallbackExecutor.execute(
+ () -> mLifecycleCallback.onSessionOpened(activeSession));
+ }
+ }
+
+ @Override
+ public void onSessionClosed(int sessionId, int reason) throws RemoteException {
+ final HubEndpointSession activeSession;
+
+ // Retrieve the active session
+ synchronized (mLock) {
+ activeSession = mActiveSessions.get(sessionId);
+ }
+ // TODO(b/378974199): Consider refactor these assertions
+ if (activeSession == null) {
+ Log.i(TAG, "onSessionClosed: session not active, id=" + sessionId);
+ return;
+ }
+
+ // Execute the callback
+ if (mLifecycleCallback != null) {
+ mLifecycleCallbackExecutor.execute(
+ () -> {
+ mLifecycleCallback.onSessionClosed(activeSession, reason);
+
+ // Remove the session object first to call
+ activeSession.setClosed();
+ synchronized (mLock) {
+ mActiveSessions.remove(sessionId);
+ }
+ });
+ }
+ }
+ };
+
+ /** Binder returned from system service, non-null while registered. */
+ @Nullable private IContextHubEndpoint mServiceToken;
+
+ /** HubEndpointInfo with the assigned endpoint id from system service. */
+ @Nullable private HubEndpointInfo mAssignedHubEndpointInfo;
+
+ private HubEndpoint(
+ @NonNull HubEndpointInfo pendingEndpointInfo,
+ @Nullable IHubEndpointLifecycleCallback endpointLifecycleCallback,
+ @NonNull Executor lifecycleCallbackExecutor) {
+ mPendingHubEndpointInfo = pendingEndpointInfo;
+ mLifecycleCallback = endpointLifecycleCallback;
+ mLifecycleCallbackExecutor = lifecycleCallbackExecutor;
+ }
+
+ /** @hide */
+ public void register(IContextHubService service) {
+ // TODO(b/378974199): Consider refactor these assertions
+ if (mServiceToken != null) {
+ // Already registered
+ return;
+ }
+ try {
+ IContextHubEndpoint serviceToken =
+ service.registerEndpoint(mPendingHubEndpointInfo, mServiceCallback);
+ mAssignedHubEndpointInfo = serviceToken.getAssignedHubEndpointInfo();
+ mServiceToken = serviceToken;
+ } catch (RemoteException e) {
+ Log.e(TAG, "registerEndpoint: failed to register endpoint", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public void unregister() {
+ IContextHubEndpoint serviceToken = mServiceToken;
+ // TODO(b/378974199): Consider refactor these assertions
+ if (serviceToken == null) {
+ // Not yet registered
+ return;
+ }
+
+ try {
+ synchronized (mLock) {
+ // Don't call HubEndpointSession.close() here.
+ for (int i = 0; i < mActiveSessions.size(); i++) {
+ mActiveSessions.get(mActiveSessions.keyAt(i)).setClosed();
+ }
+ mActiveSessions.clear();
+ }
+ mServiceToken.unregister();
+ } catch (RemoteException e) {
+ Log.e(TAG, "unregisterEndpoint: failed to unregister endpoint", e);
+ e.rethrowFromSystemServer();
+ } finally {
+ mServiceToken = null;
+ mAssignedHubEndpointInfo = null;
+ }
+ }
+
+ /** @hide */
+ public void openSession(HubEndpointInfo destinationInfo) {
+ // TODO(b/378974199): Consider refactor these assertions
+ if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
+ // No longer registered?
+ return;
+ }
+
+ HubEndpointSession newSession;
+ try {
+ // Request system service to assign session id.
+ int sessionId = mServiceToken.openSession(destinationInfo);
+
+ // Save the newly created session
+ synchronized (mLock) {
+ newSession =
+ new HubEndpointSession(
+ sessionId,
+ HubEndpoint.this,
+ destinationInfo,
+ mAssignedHubEndpointInfo);
+ mActiveSessions.put(sessionId, newSession);
+ }
+ } catch (RemoteException e) {
+ // Move this to toString
+ Log.e(TAG, "openSession: failed to open session to " + destinationInfo, e);
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public void closeSession(HubEndpointSession session) {
+ IContextHubEndpoint serviceToken = mServiceToken;
+ // TODO(b/378974199): Consider refactor these assertions
+ if (serviceToken == null || mAssignedHubEndpointInfo == null) {
+ // Not registered
+ return;
+ }
+
+ synchronized (mLock) {
+ if (!mActiveSessions.contains(session.getId())) {
+ // Already closed?
+ return;
+ }
+ session.setClosed();
+ mActiveSessions.remove(session.getId());
+ }
+
+ try {
+ // Oneway notification to system service
+ serviceToken.closeSession(
+ session.getId(),
+ IHubEndpointLifecycleCallback.REASON_CLOSE_ENDPOINT_SESSION_REQUESTED);
+ } catch (RemoteException e) {
+ Log.e(TAG, "closeSession: failed to close session " + session, e);
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ @Nullable
+ public String getTag() {
+ return mPendingHubEndpointInfo.getTag();
+ }
+
+ @Nullable
+ public IHubEndpointLifecycleCallback getLifecycleCallback() {
+ return mLifecycleCallback;
+ }
+
+ /** Builder for a {@link HubEndpoint} object. */
+ public static final class Builder {
+ private final String mPackageName;
+
+ @Nullable private IHubEndpointLifecycleCallback mLifecycleCallback;
+
+ @NonNull private Executor mLifecycleCallbackExecutor;
+
+ @Nullable private String mTag;
+
+ /** Create a builder for {@link HubEndpoint} */
+ public Builder(@NonNull Context context) {
+ mPackageName = context.getPackageName();
+ mLifecycleCallbackExecutor = context.getMainExecutor();
+ }
+
+ /**
+ * Set a tag string. The tag can be used to further identify the creator of the endpoint.
+ * Endpoints created by the same package share the same name but should have different tags.
+ */
+ @NonNull
+ public Builder setTag(@NonNull String tag) {
+ mTag = tag;
+ return this;
+ }
+
+ /** Attach a callback interface for lifecycle events for this Endpoint */
+ @NonNull
+ public Builder setLifecycleCallback(
+ @NonNull IHubEndpointLifecycleCallback lifecycleCallback) {
+ mLifecycleCallback = lifecycleCallback;
+ return this;
+ }
+
+ /**
+ * Attach a callback interface for lifecycle events for this Endpoint with a specified
+ * executor
+ */
+ @NonNull
+ public Builder setLifecycleCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull IHubEndpointLifecycleCallback lifecycleCallback) {
+ mLifecycleCallbackExecutor = executor;
+ mLifecycleCallback = lifecycleCallback;
+ return this;
+ }
+
+ /** Build the {@link HubEndpoint} object. */
+ @NonNull
+ public HubEndpoint build() {
+ return new HubEndpoint(
+ new HubEndpointInfo(mPackageName, mTag),
+ mLifecycleCallback,
+ mLifecycleCallbackExecutor);
+ }
+ }
+}
diff --git a/core/java/android/hardware/contexthub/HubEndpointInfo.java b/core/java/android/hardware/contexthub/HubEndpointInfo.java
index c17fc00..ed8ff29 100644
--- a/core/java/android/hardware/contexthub/HubEndpointInfo.java
+++ b/core/java/android/hardware/contexthub/HubEndpointInfo.java
@@ -115,6 +115,13 @@
mTag = endpointInfo.tag;
}
+ /** @hide */
+ public HubEndpointInfo(String name, @Nullable String tag) {
+ mId = HubEndpointIdentifier.invalid();
+ mName = name;
+ mTag = tag;
+ }
+
private HubEndpointInfo(Parcel in) {
long hubId = in.readLong();
long endpointId = in.readLong();
diff --git a/core/java/android/hardware/contexthub/HubEndpointSession.java b/core/java/android/hardware/contexthub/HubEndpointSession.java
new file mode 100644
index 0000000..ef989f1f
--- /dev/null
+++ b/core/java/android/hardware/contexthub/HubEndpointSession.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.contexthub;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.chre.flags.Flags;
+import android.util.CloseGuard;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * An object representing a communication session between two different hub endpoints.
+ *
+ * <p>A published enpoint can receive
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OFFLOAD_API)
+public class HubEndpointSession implements AutoCloseable {
+ private final CloseGuard mCloseGuard = new CloseGuard();
+
+ private final int mId;
+
+ // TODO(b/377717509): Implement Message sending API & interface
+ @NonNull private final HubEndpoint mHubEndpoint;
+ @NonNull private final HubEndpointInfo mInitiator;
+ @NonNull private final HubEndpointInfo mDestination;
+
+ private final AtomicBoolean mIsClosed = new AtomicBoolean(true);
+
+ /** @hide */
+ HubEndpointSession(
+ int id,
+ @NonNull HubEndpoint hubEndpoint,
+ @NonNull HubEndpointInfo destination,
+ @NonNull HubEndpointInfo initiator) {
+ mId = id;
+ mHubEndpoint = hubEndpoint;
+ mDestination = destination;
+ mInitiator = initiator;
+ }
+
+ /** @hide */
+ public int getId() {
+ return mId;
+ }
+
+ /** @hide */
+ public void setOpened() {
+ mIsClosed.set(false);
+ mCloseGuard.open("close");
+ }
+
+ /** @hide */
+ public void setClosed() {
+ mIsClosed.set(true);
+ mCloseGuard.close();
+ }
+
+ /**
+ * Closes the connection for this session between an endpoint and the Context Hub Service.
+ *
+ * <p>When this function is invoked, the messaging associated with this session is invalidated.
+ * All futures messages targeted for this client are dropped.
+ */
+ public void close() {
+ if (!mIsClosed.getAndSet(true)) {
+ mCloseGuard.close();
+ mHubEndpoint.closeSession(this);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append("Session [");
+ stringBuilder.append(mId);
+ stringBuilder.append("]: [");
+ stringBuilder.append(mInitiator);
+ stringBuilder.append("]->[");
+ stringBuilder.append(mDestination);
+ stringBuilder.append("]");
+ return stringBuilder.toString();
+ }
+
+ /** @hide */
+ protected void finalize() throws Throwable {
+ try {
+ // Note that guard could be null if the constructor threw.
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+}
diff --git a/core/java/android/hardware/contexthub/HubEndpointSessionResult.java b/core/java/android/hardware/contexthub/HubEndpointSessionResult.java
new file mode 100644
index 0000000..1f2bdb9
--- /dev/null
+++ b/core/java/android/hardware/contexthub/HubEndpointSessionResult.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.contexthub;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.chre.flags.Flags;
+
+/**
+ * Return type of {@link IHubEndpointLifecycleCallback#onSessionOpenRequest}. The value determines
+ * whether a open session request from the remote is accepted or not.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OFFLOAD_API)
+public class HubEndpointSessionResult {
+ private final boolean mAccepted;
+
+ @Nullable private final String mReason;
+
+ private HubEndpointSessionResult(boolean accepted, @Nullable String reason) {
+ mAccepted = accepted;
+ mReason = reason;
+ }
+
+ /**
+ * Retrieve the decision of the session request.
+ *
+ * @return Whether a session request was accepted or not, previously set with {@link #accept()}
+ * or {@link #reject(String)}.
+ */
+ public boolean isAccepted() {
+ return mAccepted;
+ }
+
+ /**
+ * Retrieve the decision of the session request.
+ *
+ * @return The reason previously set in {@link #reject(String)}. If the result was {@link
+ * #accept()}, the reason will be null.
+ */
+ @Nullable
+ public String getReason() {
+ return mReason;
+ }
+
+ /** Accept the request. */
+ @NonNull
+ public static HubEndpointSessionResult accept() {
+ return new HubEndpointSessionResult(true, null);
+ }
+
+ /**
+ * Reject the request with a reason.
+ *
+ * @param reason Reason why the request was rejected, for diagnostic purposes.
+ */
+ @NonNull
+ public static HubEndpointSessionResult reject(@NonNull String reason) {
+ return new HubEndpointSessionResult(false, reason);
+ }
+}
diff --git a/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
new file mode 100644
index 0000000..61e60e3
--- /dev/null
+++ b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.contexthub;
+
+import android.hardware.contexthub.HubEndpointInfo;
+
+/**
+ * @hide
+ */
+interface IContextHubEndpoint {
+ /**
+ * Retrieve the up-to-date EndpointInfo, with assigned endpoint id.
+ */
+ HubEndpointInfo getAssignedHubEndpointInfo();
+
+ /**
+ * Request system service to open a session with a specific destination.
+ *
+ * @param destination A valid HubEndpointInfo representing the destination.
+ *
+ * @throws IllegalArgumentException If the HubEndpointInfo is not valid.
+ * @throws IllegalStateException If there are too many opened sessions.
+ */
+ int openSession(in HubEndpointInfo destination);
+
+ /**
+ * Request system service to close a specific session
+ *
+ * @param sessionId An integer identifying the session, assigned by system service
+ * @param reason An integer identifying the reason
+ *
+ * @throws IllegalStateException If the session wasn't opened.
+ */
+ void closeSession(int sessionId, int reason);
+
+ /**
+ * Callback when a session is opened. This callback is the status callback for a previous
+ * IContextHubEndpointCallback.onSessionOpenRequest().
+ *
+ * @param sessionId The integer representing the communication session, previously set in
+ * onSessionOpenRequest().
+ *
+ * @throws IllegalStateException If the session wasn't opened.
+ */
+ void openSessionRequestComplete(int sessionId);
+
+ /**
+ * Unregister this endpoint from the HAL, invalidate the EndpointInfo previously assigned.
+ */
+ void unregister();
+}
diff --git a/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl b/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl
new file mode 100644
index 0000000..5656a4a
--- /dev/null
+++ b/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.contexthub;
+
+import android.hardware.contexthub.HubEndpointInfo;
+
+/**
+ * @hide
+ */
+oneway interface IContextHubEndpointCallback {
+ /**
+ * Request from system service to open a session, requested by a specific initiator.
+ *
+ * @param sessionId An integer identifying the session, assigned by the initiator
+ * @param initiator HubEndpointInfo representing the requester
+ */
+ void onSessionOpenRequest(int sessionId, in HubEndpointInfo initiator);
+
+ /**
+ * Request from system service to close a specific session
+ *
+ * @param sessionId An integer identifying the session
+ * @param reason An integer identifying the reason
+ */
+ void onSessionClosed(int sessionId, int reason);
+
+
+ /**
+ * Notifies the system service that the session requested by IContextHubEndpoint.openSession
+ * is ready to use.
+ *
+ * @param sessionId The integer representing the communication session, previously set in
+ * IContextHubEndpoint.openSession(). This id is assigned by the HAL.
+ */
+ void onSessionOpenComplete(int sessionId);
+}
diff --git a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java b/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
new file mode 100644
index 0000000..5bd3c0e
--- /dev/null
+++ b/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
@@ -0,0 +1,78 @@
+/*
+ * 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.hardware.contexthub;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.chre.flags.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Interface for listening to lifecycle events of a hub endpoint.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OFFLOAD_API)
+public interface IHubEndpointLifecycleCallback {
+ /** Unknown reason. */
+ int REASON_UNSPECIFIED = 0;
+
+ /** The peer rejected the request to open this endpoint session. */
+ int REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED = 3;
+
+ /** The peer closed this endpoint session. */
+ int REASON_CLOSE_ENDPOINT_SESSION_REQUESTED = 4;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ REASON_UNSPECIFIED,
+ REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED,
+ REASON_CLOSE_ENDPOINT_SESSION_REQUESTED,
+ })
+ @interface EndpointLifecycleReason {}
+
+ /**
+ * Called when an endpoint is requesting a session be opened with another endpoint.
+ *
+ * @param requester The {@link HubEndpointInfo} object representing the requester
+ */
+ @NonNull
+ HubEndpointSessionResult onSessionOpenRequest(@NonNull HubEndpointInfo requester);
+
+ /**
+ * Called when a communication session is opened and ready to be used.
+ *
+ * @param session The {@link HubEndpointSession} object that can be used for communication
+ */
+ void onSessionOpened(@NonNull HubEndpointSession session);
+
+ /**
+ * Called when a communication session is requested to be closed, or the peer endpoint rejected
+ * the session open request.
+ *
+ * @param session The {@link HubEndpointSession} object that is now closed and shouldn't be
+ * used.
+ * @param reason The reason why this session was closed.
+ */
+ void onSessionClosed(@NonNull HubEndpointSession session, @EndpointLifecycleReason int reason);
+}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index e6a1640..25327a9 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -61,6 +61,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.display.feature.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -102,6 +103,7 @@
private final WeakDisplayCache mDisplayCache = new WeakDisplayCache();
private int mDisplayIdToMirror = INVALID_DISPLAY;
+ private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
/**
* Broadcast receiver that indicates when the Wifi display status changes.
@@ -1613,6 +1615,17 @@
}
/**
+ * Returns whether this device supports Always On Display.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_IS_ALWAYS_ON_AVAILABLE_API)
+ public boolean isAlwaysOnDisplayCurrentlyAvailable() {
+ return getAmbientDisplayConfiguration().alwaysOnAvailableForUser(mContext.getUserId());
+ }
+
+ /**
* Returns whether device supports seamless refresh rate switching.
*
* Match content frame rate setting has three options: seamless, non-seamless and never.
@@ -1674,6 +1687,15 @@
}
}
+ private AmbientDisplayConfiguration getAmbientDisplayConfiguration() {
+ synchronized (this) {
+ if (mAmbientDisplayConfiguration == null) {
+ mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
+ }
+ }
+ return mAmbientDisplayConfiguration;
+ }
+
/**
* Creates a VirtualDisplay that will mirror the content of displayIdToMirror
* @param name The name for the virtual display
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index be710b1..1e66bee 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -17,6 +17,7 @@
package android.hardware.display;
+import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM;
import static android.hardware.display.DisplayManager.EventFlag;
import static android.Manifest.permission.MANAGE_DISPLAYS;
import static android.view.Display.HdrCapabilities.HdrType;
@@ -188,9 +189,11 @@
}
private PropertyInvalidatedCache<Integer, DisplayInfo> mDisplayCache =
- new PropertyInvalidatedCache<Integer, DisplayInfo>(
- 8, // size of display cache
- CACHE_KEY_DISPLAY_INFO_PROPERTY) {
+ new PropertyInvalidatedCache<>(
+ new PropertyInvalidatedCache.Args(MODULE_SYSTEM)
+ .maxEntries(8).api(CACHE_KEY_DISPLAY_INFO_API).isolateUids(false),
+ CACHE_KEY_DISPLAY_INFO_API, null) {
+
@Override
public DisplayInfo recompute(Integer id) {
try {
@@ -1514,18 +1517,17 @@
}
/**
- * Name of the property containing a unique token which changes every time we update the
- * system's display configuration.
+ * The API portion of the key that identifies the unique PropertyInvalidatedCache token which
+ * changes every time we update the system's display configuration.
*/
- public static final String CACHE_KEY_DISPLAY_INFO_PROPERTY =
- PropertyInvalidatedCache.createSystemCacheKey("display_info");
+ private static final String CACHE_KEY_DISPLAY_INFO_API = "display_info";
/**
* Invalidates the contents of the display info cache for all applications. Can only
* be called by system_server.
*/
public static void invalidateLocalDisplayInfoCaches() {
- PropertyInvalidatedCache.invalidateCache(CACHE_KEY_DISPLAY_INFO_PROPERTY);
+ PropertyInvalidatedCache.invalidateCache(MODULE_SYSTEM, CACHE_KEY_DISPLAY_INFO_API);
}
/**
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index e009c2f..b2c3bb8 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -35,7 +35,9 @@
import android.content.pm.PackageManager;
import android.hardware.contexthub.ErrorCode;
import android.hardware.contexthub.HubDiscoveryInfo;
+import android.hardware.contexthub.HubEndpoint;
import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.IHubEndpointLifecycleCallback;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
@@ -1036,6 +1038,55 @@
}
/**
+ * Registers an endpoint and its callback with the Context Hub Service.
+ *
+ * <p>An endpoint is registered with the Context Hub Service and published to the HAL. When the
+ * registration succeeds, the endpoint can receive notifications through the provided callback.
+ *
+ * @param hubEndpoint {@link HubEndpoint} object created by {@link HubEndpoint.Builder}
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @FlaggedApi(Flags.FLAG_OFFLOAD_API)
+ public void registerEndpoint(@NonNull HubEndpoint hubEndpoint) {
+ hubEndpoint.register(mService);
+ }
+
+ /**
+ * Use a registered endpoint to connect to another endpoint (destination).
+ *
+ * <p>Context Hub Service will create the endpoint session and notify the registered endpoint.
+ * The registered endpoint will receive callbacks on its {@link IHubEndpointLifecycleCallback}
+ * object regarding the lifecycle events of the session
+ *
+ * @param hubEndpoint {@link HubEndpoint} object previously registered via {@link
+ * ContextHubManager#registerEndpoint(HubEndpoint)}.
+ * @param destination {@link HubEndpointInfo} object that represents an endpoint from previous
+ * endpoint discovery results (e.g. from {@link ContextHubManager#findEndpoints(long)}).
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @FlaggedApi(Flags.FLAG_OFFLOAD_API)
+ public void openSession(
+ @NonNull HubEndpoint hubEndpoint, @NonNull HubEndpointInfo destination) {
+ hubEndpoint.openSession(destination);
+ }
+
+ /**
+ * Unregisters an endpoint and its callback with the Context Hub Service.
+ *
+ * <p>An endpoint is unregistered from the HAL. The endpoint object will no longer receive
+ * notification through the provided callback.
+ *
+ * @param hubEndpoint {@link HubEndpoint} object created by {@link HubEndpoint.Builder}. This
+ * should match a previously registered object via {@link
+ * ContextHubManager#registerEndpoint(HubEndpoint)}.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @FlaggedApi(Flags.FLAG_OFFLOAD_API)
+ public void unregisterEndpoint(@NonNull HubEndpoint hubEndpoint) {
+ hubEndpoint.unregister();
+ }
+
+ /**
* Queries for the list of preloaded nanoapp IDs on the system.
*
* @param hubInfo The Context Hub to query a list of nanoapp IDs from.
@@ -1194,6 +1245,7 @@
requireNonNull(mainLooper, "mainLooper cannot be null");
mService = service;
mMainLooper = mainLooper;
+
try {
mService.registerCallback(mClientCallback);
} catch (RemoteException e) {
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index fc6a70a..5128723 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -19,6 +19,8 @@
// Declare any non-default types here with import statements
import android.app.PendingIntent;
import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.IContextHubEndpoint;
+import android.hardware.contexthub.IContextHubEndpointCallback;
import android.hardware.location.ContextHubInfo;
import android.hardware.location.ContextHubMessage;
import android.hardware.location.HubInfo;
@@ -127,4 +129,8 @@
// Finds all endpoints that havea specific ID
@EnforcePermission("ACCESS_CONTEXT_HUB")
List<HubEndpointInfo> findEndpoints(long endpointId);
+
+ // Register an endpoint with the context hub
+ @EnforcePermission("ACCESS_CONTEXT_HUB")
+ IContextHubEndpoint registerEndpoint(in HubEndpointInfo pendingEndpointInfo, in IContextHubEndpointCallback callback);
}
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index 7529ab9..036ccd8 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -19,6 +19,8 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.TestApi;
+import android.app.ActivityThread;
+import android.app.Instrumentation;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Process;
import android.os.UserHandle;
@@ -31,7 +33,6 @@
import android.util.proto.ProtoOutputStream;
import dalvik.annotation.optimization.NeverCompile;
-import dalvik.system.VMDebug;
import java.io.FileDescriptor;
import java.lang.annotation.Retention;
@@ -111,11 +112,39 @@
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
MessageQueue(boolean quitAllowed) {
- mUseConcurrent = UserHandle.isCore(Process.myUid()) && !VMDebug.isDebuggingEnabled();
+ // Concurrent mode modifies behavior that is observable via reflection and is commonly used
+ // by tests.
+ // For now, we limit it to system processes to avoid breaking apps and their tests.
+ mUseConcurrent = UserHandle.isCore(Process.myUid());
+ // Even then, we don't use it if instrumentation is loaded as it breaks some
+ // platform tests.
+ final Instrumentation instrumentation = getInstrumentation();
+ mUseConcurrent &= instrumentation == null || !instrumentation.isInstrumenting();
+ // We can lift this restriction in the future after we've made it possible for test authors
+ // to test Looper and MessageQueue without resorting to reflection.
+
+ // Holdback study.
+ if (mUseConcurrent && Flags.messageQueueForceLegacy()) {
+ mUseConcurrent = false;
+ }
+
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
+ @android.ravenwood.annotation.RavenwoodReplace(blockedBy = ActivityThread.class)
+ private static Instrumentation getInstrumentation() {
+ final ActivityThread activityThread = ActivityThread.currentActivityThread();
+ if (activityThread != null) {
+ return activityThread.getInstrumentation();
+ }
+ return null;
+ }
+
+ private static Instrumentation getInstrumentation$ravenwood() {
+ return null; // Instrumentation not supported on Ravenwood yet.
+ }
+
@Override
protected void finalize() throws Throwable {
try {
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 24e1d66..e63b664 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -128,3 +128,6 @@
# Dropbox
per-file DropBoxManager* = mwachens@google.com
+
+# Flags
+per-file flags.aconfig = file:/FF_LEADS_OWNERS
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 118167d..0144506 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -4,6 +4,15 @@
# keep-sorted start block=yes newline_separated=yes
flag {
+ # Holdback study for concurrent MessageQueue.
+ # Do not promote beyond trunkfood.
+ namespace: "system_performance"
+ name: "message_queue_force_legacy"
+ description: "Whether to holdback concurrent MessageQueue (force legacy)."
+ bug: "336880969"
+}
+
+flag {
name: "adpf_gpu_report_actual_work_duration"
is_exported: true
namespace: "game"
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 6264fbb..0a35fe3 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -399,4 +399,13 @@
namespace: "supervision"
description: "This flag is used to enable all the remaining permissions required to the supervision role"
bug: "367333883"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "permission_request_short_circuit_enabled"
+ is_fixed_read_only: true
+ is_exported: true
+ namespace: "permissions"
+ description: "This flag is used to short circuit the request for permananently denied permissions"
+ bug: "378923900"
+}
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index 98dda10..14a14e6 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -16,8 +16,10 @@
package android.service.autofill;
+import static android.service.autofill.Flags.FLAG_AUTOFILL_W_METRICS;
import static android.view.autofill.Helper.sVerbose;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -63,14 +65,21 @@
private static final String TAG = "FillEventHistory";
/**
- * Not in parcel. The ID of the autofill session that created the {@link FillResponse}.
+ * The ID of the autofill session that created the {@link FillResponse}.
+ *
+ * TODO: add this to the parcel.
*/
private final int mSessionId;
@Nullable private final Bundle mClientState;
@Nullable List<Event> mEvents;
- /** @hide */
+ /**
+ * Returns the unique identifier of this FillEventHistory.
+ *
+ * <p>This is used to differentiate individual FillEventHistory.
+ */
+ @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
public int getSessionId() {
return mSessionId;
}
@@ -283,6 +292,13 @@
/** All fields matched contents of datasets. */
public static final int NO_SAVE_UI_REASON_DATASET_MATCH = 6;
+ /**
+ * Credential Manager is invoked instead of Autofill. When that happens, Save Dialog cannot
+ * be shown, and this will be populated in
+ */
+ @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+ public static final int NO_SAVE_UI_REASON_USING_CREDMAN = 7;
+
/** @hide */
@IntDef(prefix = { "NO_SAVE_UI_REASON_" }, value = {
NO_SAVE_UI_REASON_NONE,
@@ -310,11 +326,20 @@
public static final int UI_TYPE_DIALOG = 3;
/**
- * The autofill suggestion is shown os a credman bottom sheet
- * @hide
+ * The autofill suggestion is shown os a credman bottom sheet
+ *
+ * <p>Note, this was introduced as bottom sheet even though it applies to all credman UI
+ * types. Instead of exposing this directly to the public, the generic UI_TYPE_CREDMAN is
+ * introduced with the same number.
+ *
+ * @hide
*/
public static final int UI_TYPE_CREDMAN_BOTTOM_SHEET = 4;
+ /** Credential Manager suggestions are shown instead of Autofill suggestion */
+ @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+ public static final int UI_TYPE_CREDMAN = 4;
+
/** @hide */
@IntDef(prefix = { "UI_TYPE_" }, value = {
UI_TYPE_UNKNOWN,
@@ -359,6 +384,13 @@
return mEventType;
}
+ /** Gets the {@code AutofillId} that's focused at the time of action */
+ @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+ @Nullable
+ public AutofillId getFocusedId() {
+ return null;
+ }
+
/**
* Returns the id of dataset the id was on.
*
@@ -391,6 +423,17 @@
}
/**
+ * Returns which datasets were shown to the user.
+ *
+ * <p><b>Note: </b>Only set on events of type {@link #TYPE_DATASETS_SHOWN}.
+ */
+ @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+ @NonNull
+ public Set<String> getShownDatasetIds() {
+ return Collections.emptySet();
+ }
+
+ /**
* Returns which datasets were NOT selected by the user.
*
* <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
diff --git a/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl b/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl
index 0dca78d..03e27b8 100644
--- a/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl
+++ b/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl
@@ -43,4 +43,6 @@
oneway void unregisterWalletServiceEventListener(in WalletServiceEventListenerRequest request);
// Request to get a PendingIntent to launch an activity from which the user can manage their cards.
oneway void onTargetActivityIntentRequested(in IQuickAccessWalletServiceCallbacks callbacks);
+ // Request to get a PendingIntent to launch an activity, triggered when the user performs a gesture.
+ oneway void onGestureTargetActivityIntentRequested(in IQuickAccessWalletServiceCallbacks callbacks);
}
\ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl b/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl
index 1b69ca1..61d7fd1 100644
--- a/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl
+++ b/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl
@@ -37,4 +37,6 @@
oneway void onWalletServiceEvent(in WalletServiceEvent event);
// Called in response to onTargetActivityIntentRequested. May only be called once per request.
oneway void onTargetActivityPendingIntentReceived(in PendingIntent pendingIntent);
+ //Called in response to onGesturePendingIntent
+ oneway void onGestureTargetActivityPendingIntentReceived(in PendingIntent pendingIntent);
}
\ No newline at end of file
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
index faa5b2f..b5251db 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
@@ -17,6 +17,7 @@
package android.service.quickaccesswallet;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
@@ -181,6 +182,23 @@
}
/**
+ * Gets the {@link PendingIntent} provided by QuickAccessWalletService to be sent when the user
+ * launches Wallet via gesture.
+ */
+ @FlaggedApi(Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ void getGestureTargetActivityPendingIntent(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull GesturePendingIntentCallback gesturePendingIntentCallback);
+
+ /** Callback interface for getGesturePendingIntent. */
+ @FlaggedApi(Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ interface GesturePendingIntentCallback {
+ /** Callback method for getGesturePendingIntent */
+ @FlaggedApi(Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ void onGesturePendingIntentRetrieved(@Nullable PendingIntent pendingIntent);
+ }
+
+ /**
* The manifest entry for the QuickAccessWalletService may also publish information about the
* activity that hosts the Wallet view. This is typically the home screen of the Wallet
* application.
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
index a59f026..97a4bef 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
@@ -267,6 +267,34 @@
}
@Override
+ public void getGestureTargetActivityPendingIntent(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull GesturePendingIntentCallback gesturePendingIntentCallback) {
+ BaseCallbacks callbacks =
+ new BaseCallbacks() {
+ @Override
+ public void onGestureTargetActivityPendingIntentReceived(
+ PendingIntent pendingIntent) {
+ if (!Flags.launchWalletOptionOnPowerDoubleTap()) {
+ return;
+ }
+ executor.execute(
+ () ->
+ gesturePendingIntentCallback
+ .onGesturePendingIntentRetrieved(pendingIntent));
+ }
+ };
+
+ executeApiCall(
+ new ApiCaller("getGestureTargetActivityPendingIntent") {
+ @Override
+ void performApiCall(IQuickAccessWalletService service) throws RemoteException {
+ service.onGestureTargetActivityIntentRequested(callbacks);
+ }
+ });
+ }
+
+ @Override
@Nullable
public Intent createWalletSettingsIntent() {
if (mServiceInfo == null) {
@@ -506,5 +534,9 @@
public void onTargetActivityPendingIntentReceived(PendingIntent pendingIntent) {
throw new IllegalStateException();
}
+
+ public void onGestureTargetActivityPendingIntentReceived(PendingIntent pendingIntent) {
+ throw new IllegalStateException();
+ }
}
}
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
index 36fa21c..90136ae 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
@@ -16,6 +16,9 @@
package android.service.quickaccesswallet;
+import static android.service.quickaccesswallet.Flags.launchWalletOptionOnPowerDoubleTap;
+
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
@@ -247,6 +250,19 @@
callbacks));
}
+ @FlaggedApi(Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ @Override
+ public void onGestureTargetActivityIntentRequested(
+ @NonNull IQuickAccessWalletServiceCallbacks callbacks) {
+ if (launchWalletOptionOnPowerDoubleTap()) {
+ mHandler.post(
+ () ->
+ QuickAccessWalletService.this
+ .onGestureTargetActivityIntentRequestedInternal(
+ callbacks));
+ }
+ }
+
public void registerWalletServiceEventListener(
@NonNull WalletServiceEventListenerRequest request,
@NonNull IQuickAccessWalletServiceCallbacks callback) {
@@ -275,6 +291,20 @@
}
}
+ private void onGestureTargetActivityIntentRequestedInternal(
+ IQuickAccessWalletServiceCallbacks callbacks) {
+ if (!Flags.launchWalletOptionOnPowerDoubleTap()) {
+ return;
+ }
+
+ try {
+ callbacks.onGestureTargetActivityPendingIntentReceived(
+ getGestureTargetActivityPendingIntent());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error", e);
+ }
+ }
+
@Override
@Nullable
public IBinder onBind(@NonNull Intent intent) {
@@ -349,6 +379,18 @@
return null;
}
+ /**
+ * Specify a {@link PendingIntent} to be launched on user gesture.
+ *
+ * <p>The pending intent will be sent when the user performs a gesture to open Wallet.
+ * The pending intent should launch an activity.
+ */
+ @Nullable
+ @FlaggedApi(Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public PendingIntent getGestureTargetActivityPendingIntent() {
+ return null;
+ }
+
private void sendWalletServiceEventInternal(WalletServiceEvent serviceEvent) {
if (mEventListener == null) {
Log.i(TAG, "No dismiss listener registered");
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 032f592..cb72b97 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -50,6 +50,7 @@
import android.text.style.LineBreakConfigSpan;
import android.text.style.LineHeightSpan;
import android.text.style.LocaleSpan;
+import android.text.style.NoWritingToolsSpan;
import android.text.style.ParagraphStyle;
import android.text.style.QuoteSpan;
import android.text.style.RelativeSizeSpan;
@@ -817,7 +818,9 @@
/** @hide */
public static final int LINE_BREAK_CONFIG_SPAN = 30;
/** @hide */
- public static final int LAST_SPAN = LINE_BREAK_CONFIG_SPAN;
+ public static final int NO_WRITING_TOOLS_SPAN = 31;
+ /** @hide */
+ public static final int LAST_SPAN = NO_WRITING_TOOLS_SPAN;
/**
* Flatten a CharSequence and whatever styles can be copied across processes
@@ -1025,6 +1028,10 @@
span = LineBreakConfigSpan.CREATOR.createFromParcel(p);
break;
+ case NO_WRITING_TOOLS_SPAN:
+ span = NoWritingToolsSpan.CREATOR.createFromParcel(p);
+ break;
+
default:
throw new RuntimeException("bogus span encoding " + kind);
}
diff --git a/core/java/android/text/style/NoWritingToolsSpan.java b/core/java/android/text/style/NoWritingToolsSpan.java
new file mode 100644
index 0000000..90f85aa
--- /dev/null
+++ b/core/java/android/text/style/NoWritingToolsSpan.java
@@ -0,0 +1,87 @@
+/*
+ * 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.style;
+
+import static android.view.inputmethod.Flags.FLAG_WRITING_TOOLS;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.text.ParcelableSpan;
+import android.text.TextUtils;
+
+/**
+ * A span that signals to IMEs that writing tools should not modify the text.
+ *
+ * <p>For example, a text field may contain a mix of user input text and quoted text. The app
+ * can apply {@code NoWritingToolsSpan} to the quoted text so that the IME knows that writing
+ * tools should only rewrite the user input text, and not modify the quoted text.
+ */
+@FlaggedApi(FLAG_WRITING_TOOLS)
+public final class NoWritingToolsSpan implements ParcelableSpan {
+
+ /**
+ * Creates a {@link NoWritingToolsSpan}.
+ */
+ public NoWritingToolsSpan() {
+ }
+
+ @Override
+ public int getSpanTypeId() {
+ return getSpanTypeIdInternal();
+ }
+
+ /** @hide */
+ @Override
+ public int getSpanTypeIdInternal() {
+ return TextUtils.NO_WRITING_TOOLS_SPAN;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ writeToParcelInternal(dest, flags);
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcelInternal(@NonNull Parcel dest, int flags) {
+ }
+
+ @Override
+ public String toString() {
+ return "NoWritingToolsSpan{}";
+ }
+
+ @NonNull
+ public static final Creator<NoWritingToolsSpan> CREATOR = new Creator<>() {
+
+ @Override
+ public NoWritingToolsSpan createFromParcel(Parcel source) {
+ return new NoWritingToolsSpan();
+ }
+
+ @Override
+ public NoWritingToolsSpan[] newArray(int size) {
+ return new NoWritingToolsSpan[size];
+ }
+ };
+}
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 5a71282..8cb96ae 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -930,8 +930,8 @@
// of the next vsync event.
int totalFrameDelays = mBufferStuffingData.numberFrameDelays
+ mBufferStuffingData.numberWaitsForNextVsync + 1;
- long vsyncsSinceLastCallback =
- (frameTimeNanos - mLastNoOffsetFrameTimeNanos) / mLastFrameIntervalNanos;
+ long vsyncsSinceLastCallback = mLastFrameIntervalNanos > 0
+ ? (frameTimeNanos - mLastNoOffsetFrameTimeNanos) / mLastFrameIntervalNanos : 0;
// Detected idle state due to a longer inactive period since the last vsync callback
// than the total expected number of vsync frame delays. End buffer stuffing recovery.
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 1af9387..1be7f48 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -17,6 +17,7 @@
package android.view;
import static android.service.autofill.Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION;
+import static android.service.autofill.Flags.FLAG_AUTOFILL_W_METRICS;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
@@ -79,27 +80,24 @@
* Key used for writing the type of the view that generated the virtual structure of its
* children.
*
- * For example, if the virtual structure is generated by a webview, the value would be
+ * <p>For example, if the virtual structure is generated by a webview, the value would be
* "WebView". If the virtual structure is generated by a compose view, then the value would be
* "ComposeView". The value is of type String.
*
- * This value is added to mainly help with debugging purpose.
- *
- * @hide
+ * <p>This value is added to mainly help with debugging purpose.
*/
+ @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
public static final String EXTRA_VIRTUAL_STRUCTURE_TYPE =
"android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE";
-
/**
* Key used for specifying the version of the view that generated the virtual structure for
* itself and its children
*
- * For example, if the virtual structure is generated by a webview of version "104.0.5112.69",
- * then the value should be "104.0.5112.69"
- *
- * @hide
+ * <p>For example, if the virtual structure is generated by a webview of version
+ * "104.0.5112.69", then the value should be "104.0.5112.69"
*/
+ @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
public static final String EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER =
"android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER";
diff --git a/core/java/android/view/autofill/AutofillId.java b/core/java/android/view/autofill/AutofillId.java
index 6b60858..bd27778 100644
--- a/core/java/android/view/autofill/AutofillId.java
+++ b/core/java/android/view/autofill/AutofillId.java
@@ -15,6 +15,9 @@
*/
package android.view.autofill;
+import static android.service.autofill.Flags.FLAG_AUTOFILL_W_METRICS;
+
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
@@ -111,12 +114,43 @@
return new AutofillId(flags, id.mViewId, virtualChildId, NO_SESSION);
}
- /** @hide */
+ /**
+ * Returns the assigned unique identifier of this AutofillID.
+ *
+ * See @link{android.view.View#getAutofillId()} for more information on
+ * how this is generated for native Views.
+ */
+ @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
public int getViewId() {
return mViewId;
}
/**
+ * Gets the virtual id. This is set if the view is a virtual view, most commonly set if the View
+ * is of {@link android.webkit.WebView}.
+ */
+ @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+ public int getAutofillVirtualId() {
+ return mVirtualIntId;
+ }
+
+ /** Checks whether this AutofillId represents a virtual view. */
+ @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+ public boolean isVirtual() {
+ return !isNonVirtual();
+ }
+
+ /**
+ * Checks if this node is generate as part of a {@link android.app.assist.AssistStructure}. This
+ * will usually return true if it should be used by an autofill service provider, and false
+ * otherwise.
+ */
+ @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+ public boolean isInAutofillSession() {
+ return hasSession();
+ }
+
+ /**
* Gets the virtual child id.
*
* <p>Should only be used on subsystems where such id is represented by an {@code int}
@@ -181,7 +215,12 @@
return (mFlags & FLAG_HAS_SESSION) != 0;
}
- /** @hide */
+ /**
+ * Used to get the Session identifier associated with this AutofillId.
+ *
+ * @return a non-zero integer if {@link #isInAutofillSession()} returns true
+ */
+ @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
public int getSessionId() {
return mSessionId;
}
diff --git a/core/java/android/view/flags/scroll_feedback_flags.aconfig b/core/java/android/view/flags/scroll_feedback_flags.aconfig
index b180e58..ebda4d4 100644
--- a/core/java/android/view/flags/scroll_feedback_flags.aconfig
+++ b/core/java/android/view/flags/scroll_feedback_flags.aconfig
@@ -28,5 +28,5 @@
namespace: "wear_frameworks"
name: "dynamic_view_rotary_haptics_configuration"
description: "Whether ScrollFeedbackProvider dynamically disables View-based rotary haptics."
- bug: "377998870 "
+ bug: "377998870"
}
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index e619ab0..deaf957 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -175,3 +175,12 @@
bug: "342672560"
is_fixed_read_only: true
}
+
+flag {
+ name: "adaptive_handwriting_bounds"
+ is_exported: true
+ namespace: "input_method"
+ description: "Feature flag for adaptively increasing handwriting bounds."
+ bug: "350047836"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index f474b34..eebdeadc 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -402,4 +402,11 @@
namespace: "lse_desktop_experience"
description: "Enables HSUM on desktop mode."
bug: "366397912"
+}
+
+flag {
+ name: "enable_multiple_desktops"
+ namespace: "lse_desktop_experience"
+ description: "Enable multiple desktop sessions for desktop windowing."
+ bug: "379158791"
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl
index 21c7baa..469ab48 100644
--- a/core/java/com/android/internal/backup/IBackupTransport.aidl
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -410,4 +410,15 @@
* however backups initiated by the framework will call this method to retrieve one.
*/
void getBackupManagerMonitor(in AndroidFuture<IBackupManagerMonitor> resultFuture);
+
+ /**
+ * Ask the transport whether packages that are about to be backed up or restored should not be
+ * put into a restricted mode by the framework and started normally instead. The
+ * {@code resultFuture} should be completed with a subset of the packages passed in, indicating
+ * which packages should NOT be put into restricted mode for the given operation type.
+ *
+ * @param operationType 0 for backup, 1 for restore.
+ */
+ void getPackagesThatShouldNotUseRestrictedMode(in List<String> packageNames, int operationType,
+ in AndroidFuture<List<String>> resultFuture);
}
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index 8a6e6be..6db3c4d 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -2377,6 +2377,7 @@
* Flags are separated by type and by default value. They are sorted alphabetically within each
* section.
*/
+ @SuppressWarnings("AndroidFrameworkCompatChange")
private void parseBaseAppBasicFlags(ParsingPackage pkg, TypedArray sa) {
int targetSdk = pkg.getTargetSdkVersion();
//@formatter:off
@@ -2414,12 +2415,19 @@
.setResetEnabledSettingsOnAppDataCleared(bool(false,
R.styleable.AndroidManifestApplication_resetEnabledSettingsOnAppDataCleared,
sa))
- .setOnBackInvokedCallbackEnabled(bool(false, R.styleable.AndroidManifestApplication_enableOnBackInvokedCallback, sa))
// targetSdkVersion gated
.setAllowAudioPlaybackCapture(bool(targetSdk >= Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_allowAudioPlaybackCapture, sa))
.setHardwareAccelerated(bool(targetSdk >= Build.VERSION_CODES.ICE_CREAM_SANDWICH, R.styleable.AndroidManifestApplication_hardwareAccelerated, sa))
.setRequestLegacyExternalStorage(bool(targetSdk < Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_requestLegacyExternalStorage, sa))
.setCleartextTrafficAllowed(bool(targetSdk < Build.VERSION_CODES.P, R.styleable.AndroidManifestApplication_usesCleartextTraffic, sa))
+ // CompatChange.isChangeEnabled() can't be used here because this is called during
+ // PackageManagerService initialization. PlatformCompat can't be used because this
+ // code is not guaranteed to be called from the system_server process. Therefore
+ // accessing Build.VERSION_CODES directly and suppressing
+ // AndroidFrameworkCompatChange warning
+ .setOnBackInvokedCallbackEnabled(bool(
+ targetSdk > Build.VERSION_CODES.VANILLA_ICE_CREAM,
+ R.styleable.AndroidManifestApplication_enableOnBackInvokedCallback, sa))
// Ints Default 0
.setUiOptions(anInt(R.styleable.AndroidManifestApplication_uiOptions, sa))
// Ints
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index a1c987f..eb682df 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -676,15 +676,30 @@
return internMap.get(string);
}
+ protected boolean validateGroups(ILogger logger, String[] groups) {
+ for (int i = 0; i < groups.length; i++) {
+ String group = groups[i];
+ IProtoLogGroup g = mLogGroups.get(group);
+ if (g == null) {
+ logger.log("No IProtoLogGroup named " + group);
+ return false;
+ }
+ }
+ return true;
+ }
+
private int setTextLogging(boolean value, ILogger logger, String... groups) {
+ if (!validateGroups(logger, groups)) {
+ return -1;
+ }
+
for (int i = 0; i < groups.length; i++) {
String group = groups[i];
IProtoLogGroup g = mLogGroups.get(group);
if (g != null) {
g.setLogToLogcat(value);
} else {
- logger.log("No IProtoLogGroup named " + group);
- return -1;
+ throw new RuntimeException("No IProtoLogGroup named " + group);
}
}
diff --git a/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java
index 70d148a..967a5ed 100644
--- a/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java
@@ -113,6 +113,10 @@
*/
@Override
public int startLoggingToLogcat(String[] groups, @NonNull ILogger logger) {
+ if (!validateGroups(logger, groups)) {
+ return -1;
+ }
+
mViewerConfigReader.loadViewerConfig(groups, logger);
return super.startLoggingToLogcat(groups, logger);
}
@@ -125,8 +129,19 @@
*/
@Override
public int stopLoggingToLogcat(String[] groups, @NonNull ILogger logger) {
+ if (!validateGroups(logger, groups)) {
+ return -1;
+ }
+
+ var status = super.stopLoggingToLogcat(groups, logger);
+
+ if (status != 0) {
+ throw new RuntimeException("Failed to stop logging to logcat");
+ }
+
+ // If we successfully disabled logging, unload the viewer config.
mViewerConfigReader.unloadViewerConfig(groups, logger);
- return super.stopLoggingToLogcat(groups, logger);
+ return status;
}
@Deprecated
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 1925b3a..593b982 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -2201,29 +2201,9 @@
return false;
}
- // Compute the count of data items we'll actually forward to Java.
- size_t count = 0;
- if (mRemovedVsyncId <= 0) {
- count = jankData.size();
- } else {
- for (const gui::JankData& frame : jankData) {
- if (frame.frameVsyncId <= mRemovedVsyncId) {
- count++;
- }
- }
- }
-
- if (count == 0) {
- return false;
- }
-
- jobjectArray jJankDataArray = env->NewObjectArray(count, gJankDataClassInfo.clazz, nullptr);
- for (size_t i = 0, j = 0; i < jankData.size() && j < count; i++) {
- // Filter any data for frames past our removal vsync.
- if (mRemovedVsyncId > 0 && jankData[i].frameVsyncId > mRemovedVsyncId) {
- continue;
- }
-
+ jobjectArray jJankDataArray =
+ env->NewObjectArray(jankData.size(), gJankDataClassInfo.clazz, nullptr);
+ for (size_t i = 0; i < jankData.size(); i++) {
// The exposed constants in SurfaceControl are simplified, so we need to translate the
// jank type we get from SF to what is exposed in Java.
int sfJankType = jankData[i].jankType;
@@ -2250,7 +2230,7 @@
jankData[i].frameVsyncId, javaJankType,
jankData[i].frameIntervalNs, jankData[i].scheduledAppFrameTimeNs,
jankData[i].actualAppFrameTimeNs);
- env->SetObjectArrayElement(jJankDataArray, j++, jJankData);
+ env->SetObjectArrayElement(jJankDataArray, i, jJankData);
env->DeleteLocalRef(jJankData);
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 95d07df..0042459 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7921,7 +7921,31 @@
<!-- @SystemApi Allows an application to access shared libraries.
@hide -->
<permission android:name="android.permission.ACCESS_SHARED_LIBRARIES"
- android:protectionLevel="signature|installer" />
+ android:protectionLevel="signature|installer"
+ android:featureFlag="!android.content.pm.sdk_dependency_installer" />
+
+ <!-- @SystemApi Allows an application to access shared libraries.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_SHARED_LIBRARIES"
+ android:protectionLevel="signature|installer|role"
+ android:featureFlag="android.content.pm.sdk_dependency_installer" />
+
+ <!-- @SystemApi Permission held by the system to allow binding to the dependency installer role
+ holder.
+ @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+ @hide -->
+ <permission android:name="android.permission.BIND_DEPENDENCY_INSTALLER"
+ android:protectionLevel="signature"
+ android:featureFlag="android.content.pm.sdk_dependency_installer" />
+
+ <!-- @SystemApi Allows an application to install shared libraries of types
+ {@link android.content.pm.SharedLibraryInfo#TYPE_STATIC} or
+ {@link android.content.pm.SharedLibraryInfo#TYPE_SDK_PACKAGE}.
+ @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+ @hide -->
+ <permission android:name="android.permission.INSTALL_DEPENDENCY_SHARED_LIBRARIES"
+ android:protectionLevel="signature|role"
+ android:featureFlag="android.content.pm.sdk_dependency_installer" />
<!-- Allows an app to log compat change usage.
@hide <p>Not for use by third-party applications.</p> -->
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index e6dedce..72467b3 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -10285,22 +10285,25 @@
</declare-styleable>
<!-- @hide -->
+ <attr name="modifierState">
+ <flag name="META" value="0x10000" />
+ <flag name="CTRL" value="0x1000" />
+ <flag name="ALT" value="0x02" />
+ <flag name="SHIFT" value="0x1" />
+ <flag name="SYM" value="0x4" />
+ <flag name="FUNCTION" value="0x8" />
+ <flag name="CAPS_LOCK" value="0x100000" />
+ <flag name="NUM_LOCK" value="0x200000" />
+ <flag name="SCROLL_LOCK" value="0x400000" />
+ </attr>
+
+ <!-- @hide -->
<declare-styleable name="HardwareDefinedShortcut">
<attr name="keycode" />
<!-- The values are taken from public constants for modifier state defined in
{@see KeyEvent.java}. Here we allow multiple modifier flags as value, since this
represents the modifier state -->
- <attr name="modifierState">
- <flag name="META" value="0x10000" />
- <flag name="CTRL" value="0x1000" />
- <flag name="ALT" value="0x02" />
- <flag name="SHIFT" value="0x1" />
- <flag name="SYM" value="0x4" />
- <flag name="FUNCTION" value="0x8" />
- <flag name="CAPS_LOCK" value="0x100000" />
- <flag name="NUM_LOCK" value="0x200000" />
- <flag name="SCROLL_LOCK" value="0x400000" />
- </attr>
+ <attr name="modifierState" />
<attr name="outKeycode" />
</declare-styleable>
@@ -10309,6 +10312,11 @@
<attr name="keycode" />
</declare-styleable>
+ <declare-styleable name="Bookmark">
+ <attr name="keycode" />
+ <attr name="modifierState" />
+ </declare-styleable>
+
<declare-styleable name="MediaRouteButton">
<!-- This drawable is a state list where the "activated" state
indicates active media routing. Non-activated indicates
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 7799ff9..5088b5a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4205,9 +4205,17 @@
must match the value of config_cameraLaunchGestureSensorType in OEM's HAL -->
<string translatable="false" name="config_cameraLaunchGestureSensorStringType"></string>
+ <!-- Allow the gesture to double tap the power button to trigger a target action. -->
+ <bool name="config_doubleTapPowerGestureEnabled">true</bool>
<!-- Allow the gesture to double tap the power button twice to start the camera while the device
is non-interactive. -->
<bool name="config_cameraDoubleTapPowerGestureEnabled">true</bool>
+ <!-- Allow the gesture to double tap the power button twice to launch the wallet. -->
+ <bool name="config_walletDoubleTapPowerGestureEnabled">true</bool>
+ <!-- Default target action for double tap of the power button gesture.
+ 0: Launch camera
+ 1: Launch wallet -->
+ <integer name="config_defaultDoubleTapPowerGestureAction">0</integer>
<!-- Allow the gesture to quick tap the power button multiple times to start the emergency sos
experience while the device is non-interactive. -->
@@ -7217,4 +7225,7 @@
<!-- Whether to enable fp unlock when screen turns off on udfps devices -->
<bool name="config_screen_off_udfps_enabled">false</bool>
+
+ <!-- The name of the system package that will hold the dependency installer role. -->
+ <string name="config_systemDependencyInstaller" translatable="false" />
</resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index a0bf89d..ce46c64 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -142,6 +142,9 @@
</staging-public-group>
<staging-public-group type="string" first-id="0x01b40000">
+ <!-- @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+ @hide @SystemApi -->
+ <public name="config_systemDependencyInstaller" />
</staging-public-group>
<staging-public-group type="dimen" first-id="0x01b30000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7fe0912..0e12075 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3140,9 +3140,12 @@
<!-- Gesture -->
<java-symbol type="integer" name="config_cameraLaunchGestureSensorType" />
<java-symbol type="string" name="config_cameraLaunchGestureSensorStringType" />
- <java-symbol type="bool" name="config_cameraDoubleTapPowerGestureEnabled" />
<java-symbol type="integer" name="config_cameraLiftTriggerSensorType" />
<java-symbol type="string" name="config_cameraLiftTriggerSensorStringType" />
+ <java-symbol type="bool" name="config_doubleTapPowerGestureEnabled" />
+ <java-symbol type="bool" name="config_cameraDoubleTapPowerGestureEnabled" />
+ <java-symbol type="bool" name="config_walletDoubleTapPowerGestureEnabled" />
+ <java-symbol type="integer" name="config_defaultDoubleTapPowerGestureAction" />
<java-symbol type="bool" name="config_emergencyGestureEnabled" />
<java-symbol type="bool" name="config_defaultEmergencyGestureEnabled" />
<java-symbol type="bool" name="config_defaultEmergencyGestureSoundEnabled" />
diff --git a/core/res/res/xml/bookmarks.xml b/core/res/res/xml/bookmarks.xml
index 22d0226..e735784 100644
--- a/core/res/res/xml/bookmarks.xml
+++ b/core/res/res/xml/bookmarks.xml
@@ -31,29 +31,37 @@
'u': Calculator
'y': YouTube
-->
-<bookmarks>
+<bookmarks xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<bookmark
role="android.app.role.BROWSER"
- shortcut="b" />
+ androidprv:keycode="KEYCODE_B"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_CONTACTS"
- shortcut="c" />
+ androidprv:keycode="KEYCODE_C"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_EMAIL"
- shortcut="e" />
+ androidprv:keycode="KEYCODE_E"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_CALENDAR"
- shortcut="k" />
+ androidprv:keycode="KEYCODE_K"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_MAPS"
- shortcut="m" />
+ androidprv:keycode="KEYCODE_M"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_MUSIC"
- shortcut="p" />
+ androidprv:keycode="KEYCODE_P"
+ androidprv:modifierState="META" />
<bookmark
role="android.app.role.SMS"
- shortcut="s" />
+ androidprv:keycode="KEYCODE_S"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_CALCULATOR"
- shortcut="u" />
+ androidprv:keycode="KEYCODE_U"
+ androidprv:modifierState="META" />
</bookmarks>
diff --git a/core/tests/coretests/src/android/content/pm/SharedLibraryInfoTest.java b/core/tests/coretests/src/android/content/pm/SharedLibraryInfoTest.java
new file mode 100644
index 0000000..df5cd4e
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/SharedLibraryInfoTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.content.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.List;
+
+@Presubmit
+@RunWith(JUnit4.class)
+public final class SharedLibraryInfoTest {
+
+ @Rule
+ public final CheckFlagsRule checkFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ private static final String LIBRARY_NAME = "name";
+ private static final long VERSION_MAJOR = 1L;
+ private static final List<String> CERT_DIGESTS = ImmutableList.of("digest1", "digest2");
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+ public void sharedLibraryInfo_serializedAndDeserialized_retainsCertDigestInfo() {
+ SharedLibraryInfo toParcel = new SharedLibraryInfo(LIBRARY_NAME, VERSION_MAJOR,
+ SharedLibraryInfo.TYPE_SDK_PACKAGE, CERT_DIGESTS);
+
+ SharedLibraryInfo fromParcel = parcelAndUnparcel(toParcel);
+
+ assertThat(fromParcel.getCertDigests().size()).isEqualTo(toParcel.getCertDigests().size());
+ assertThat(fromParcel.getCertDigests().get(0)).isEqualTo(toParcel.getCertDigests().get(0));
+ assertThat(fromParcel.getCertDigests().get(1)).isEqualTo(toParcel.getCertDigests().get(1));
+ }
+
+ private SharedLibraryInfo parcelAndUnparcel(SharedLibraryInfo sharedLibraryInfo) {
+ Parcel parcel = Parcel.obtain();
+ parcel.setDataPosition(0);
+ sharedLibraryInfo.writeToParcel(parcel, /* flags= */0);
+
+ parcel.setDataPosition(0);
+ return SharedLibraryInfo.CREATOR.createFromParcel(parcel);
+ }
+}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 2c166c3..9bf4d65 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -836,11 +836,13 @@
mNativeColorFilter = newNativeColorFilter;
nSetColorFilter(mNativePaint, mNativeColorFilter);
}
- if (mXfermode instanceof RuntimeXfermode) {
- long newNativeXfermode = ((RuntimeXfermode) mXfermode).createNativeInstance();
- if (newNativeXfermode != mNativeXfermode) {
- mNativeXfermode = newNativeXfermode;
- nSetXfermode(mNativePaint, mNativeXfermode);
+ if (com.android.graphics.hwui.flags.Flags.runtimeColorFiltersBlenders()) {
+ if (mXfermode instanceof RuntimeXfermode) {
+ long newNativeXfermode = ((RuntimeXfermode) mXfermode).createNativeInstance();
+ if (newNativeXfermode != mNativeXfermode) {
+ mNativeXfermode = newNativeXfermode;
+ nSetXfermode(mNativePaint, mNativeXfermode);
+ }
}
}
return mNativePaint;
@@ -1470,10 +1472,12 @@
@Nullable
private Xfermode installXfermode(Xfermode xfermode) {
- if (xfermode instanceof RuntimeXfermode) {
- mXfermode = xfermode;
- nSetXfermode(mNativePaint, ((RuntimeXfermode) xfermode).createNativeInstance());
- return xfermode;
+ if (com.android.graphics.hwui.flags.Flags.runtimeColorFiltersBlenders()) {
+ if (xfermode instanceof RuntimeXfermode) {
+ mXfermode = xfermode;
+ nSetXfermode(mNativePaint, ((RuntimeXfermode) xfermode).createNativeInstance());
+ return xfermode;
+ }
}
int newMode = (xfermode instanceof PorterDuffXfermode)
? ((PorterDuffXfermode) xfermode).porterDuffMode : PorterDuffXfermode.DEFAULT;
diff --git a/graphics/java/android/graphics/RuntimeColorFilter.java b/graphics/java/android/graphics/RuntimeColorFilter.java
index 52724ce..d112f71 100644
--- a/graphics/java/android/graphics/RuntimeColorFilter.java
+++ b/graphics/java/android/graphics/RuntimeColorFilter.java
@@ -283,6 +283,23 @@
nativeUpdateChild(getNativeInstance(), filterName, colorFilter.getNativeInstance());
}
+ /**
+ * Assigns the uniform xfermode to the provided xfermode parameter. If the shader program does
+ * not have a uniform xfermode with that name then an IllegalArgumentException is thrown.
+ *
+ * @param xfermodeName name matching the uniform declared in the AGSL program
+ * @param xfermode filter passed into the AGSL program for sampling
+ */
+ public void setInputXfermode(@NonNull String xfermodeName, @NonNull RuntimeXfermode xfermode) {
+ if (xfermodeName == null) {
+ throw new NullPointerException("The xfermodeName parameter must not be null");
+ }
+ if (xfermode == null) {
+ throw new NullPointerException("The xfermode parameter must not be null");
+ }
+ nativeUpdateChild(getNativeInstance(), xfermodeName, xfermode.createNativeInstance());
+ }
+
/** @hide */
@Override
protected long createNativeInstance() {
diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java
index 78d257f..6316c1f 100644
--- a/graphics/java/android/graphics/RuntimeShader.java
+++ b/graphics/java/android/graphics/RuntimeShader.java
@@ -18,10 +18,13 @@
import android.annotation.ColorInt;
import android.annotation.ColorLong;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.util.ArrayMap;
import android.view.Window;
+import com.android.graphics.hwui.flags.Flags;
+
import libcore.util.NativeAllocationRegistry;
/**
@@ -525,6 +528,45 @@
discardNativeInstance();
}
+ /**
+ * Assigns the uniform color filter to the provided color filter parameter. If the shader
+ * program does not have a uniform color filter with that name then an IllegalArgumentException
+ * is thrown.
+ *
+ * @param filterName name matching the uniform declared in the AGSL program
+ * @param colorFilter filter passed into the AGSL program for sampling
+ */
+ @FlaggedApi(Flags.FLAG_RUNTIME_COLOR_FILTERS_BLENDERS)
+ public void setInputColorFilter(@NonNull String filterName, @NonNull ColorFilter colorFilter) {
+ if (filterName == null) {
+ throw new NullPointerException("The filterName parameter must not be null");
+ }
+ if (colorFilter == null) {
+ throw new NullPointerException("The colorFilter parameter must not be null");
+ }
+ nativeUpdateChild(mNativeInstanceRuntimeShaderBuilder, filterName,
+ colorFilter.getNativeInstance());
+ }
+
+ /**
+ * Assigns the uniform xfermode to the provided xfermode parameter. If the shader program does
+ * not have a uniform xfermode with that name then an IllegalArgumentException is thrown.
+ *
+ * @param xfermodeName name matching the uniform declared in the AGSL program
+ * @param xfermode filter passed into the AGSL program for sampling
+ */
+ @FlaggedApi(Flags.FLAG_RUNTIME_COLOR_FILTERS_BLENDERS)
+ public void setInputXfermode(@NonNull String xfermodeName, @NonNull RuntimeXfermode xfermode) {
+ if (xfermodeName == null) {
+ throw new NullPointerException("The xfermodeName parameter must not be null");
+ }
+ if (xfermode == null) {
+ throw new NullPointerException("The xfermode parameter must not be null");
+ }
+ nativeUpdateChild(mNativeInstanceRuntimeShaderBuilder, xfermodeName,
+ xfermode.createNativeInstance());
+ }
+
/** @hide */
@Override
@@ -552,5 +594,7 @@
int value4, int count);
private static native void nativeUpdateShader(
long shaderBuilder, String shaderName, long shader);
+ private static native void nativeUpdateChild(
+ long shaderBuilder, String childName, long child);
}
diff --git a/graphics/java/android/graphics/RuntimeXfermode.java b/graphics/java/android/graphics/RuntimeXfermode.java
index f5a6568..51d97a4 100644
--- a/graphics/java/android/graphics/RuntimeXfermode.java
+++ b/graphics/java/android/graphics/RuntimeXfermode.java
@@ -288,6 +288,23 @@
nativeUpdateChild(mBuilderNativeInstance, filterName, colorFilter.getNativeInstance());
}
+ /**
+ * Assigns the uniform xfermode to the provided xfermode parameter. If the shader program does
+ * not have a uniform xfermode with that name then an IllegalArgumentException is thrown.
+ *
+ * @param xfermodeName name matching the uniform declared in the AGSL program
+ * @param xfermode xfermode function passed into the AGSL program for sampling
+ */
+ public void setInputXfermode(@NonNull String xfermodeName, @NonNull RuntimeXfermode xfermode) {
+ if (xfermodeName == null) {
+ throw new NullPointerException("The xfermodeName parameter must not be null");
+ }
+ if (xfermode == null) {
+ throw new NullPointerException("The xfermode parameter must not be null");
+ }
+ nativeUpdateChild(mBuilderNativeInstance, xfermodeName, xfermode.createNativeInstance());
+ }
+
/** @hide */
public long createNativeInstance() {
return nativeCreateNativeInstance(mBuilderNativeInstance);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
index 220fc6f..819cf34 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
@@ -94,7 +94,6 @@
*/
void scheduleBackup() {
if (!mSaveEmbeddingState) {
- // TODO(b/289875940): enabled internally for broader testing.
return;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableSplitContainerData.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableSplitContainerData.java
index cb280c5..0f1246c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableSplitContainerData.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableSplitContainerData.java
@@ -44,7 +44,6 @@
@NonNull
private final IBinder mSecondaryContainerToken;
- // TODO(b/289875940): making this as non-null once the tag can be auto-generated from the rule.
@Nullable
final String mSplitRuleTag;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskFragmentContainerData.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskFragmentContainerData.java
index a79a89a..bf342d7 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskFragmentContainerData.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskFragmentContainerData.java
@@ -25,6 +25,9 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* This class holds the Parcelable data of a {@link TaskFragmentContainer}.
*/
@@ -61,6 +64,12 @@
@NonNull
final Rect mLastRequestedBounds;
+ /**
+ * Individual associated activity tokens in different containers that should be finished on
+ * exit.
+ */
+ final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>();
+
ParcelableTaskFragmentContainerData(@NonNull IBinder token, @Nullable String overlayTag,
@Nullable IBinder associatedActivityToken) {
mToken = token;
@@ -74,6 +83,7 @@
mOverlayTag = in.readString();
mAssociatedActivityToken = in.readStrongBinder();
mLastRequestedBounds = in.readTypedObject(Rect.CREATOR);
+ in.readBinderList(mActivitiesToFinishOnExit);
}
public static final Creator<ParcelableTaskFragmentContainerData> CREATOR = new Creator<>() {
@@ -99,7 +109,7 @@
dest.writeString(mOverlayTag);
dest.writeStrongBinder(mAssociatedActivityToken);
dest.writeTypedObject(mLastRequestedBounds, flags);
+ dest.writeBinderList(mActivitiesToFinishOnExit);
}
-
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index faf73c2..5ba30dd 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -98,10 +98,20 @@
mCurrentSplitAttributes = mDefaultSplitAttributes;
if (shouldFinishPrimaryWithSecondary(splitRule)) {
- mSecondaryContainer.addContainerToFinishOnExit(mPrimaryContainer);
+ addContainerToFinishOnExitWhenRestore(mSecondaryContainer, mPrimaryContainer);
}
if (shouldFinishSecondaryWithPrimary(splitRule)) {
- mPrimaryContainer.addContainerToFinishOnExit(mSecondaryContainer);
+ addContainerToFinishOnExitWhenRestore(mPrimaryContainer, mSecondaryContainer);
+ }
+ }
+
+ private void addContainerToFinishOnExitWhenRestore(
+ @NonNull TaskFragmentContainer containerToAdd,
+ @NonNull TaskFragmentContainer containerToFinish) {
+ // If an activity was already added to be finished after the restoration, then that's it.
+ // Otherwise, add the container to finish on exit.
+ if (!containerToAdd.hasActivityToFinishOnExit(containerToFinish)) {
+ containerToAdd.addContainerToFinishOnExit(containerToFinish);
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index dc1d983..b3e003e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -96,12 +96,6 @@
new ArrayList<>();
/**
- * Individual associated activity tokens in different containers that should be finished on
- * exit.
- */
- private final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>();
-
- /**
* The launch options that was used to create this container. Must not {@link Bundle#isEmpty()}
* for {@link #isOverlay()} container.
*/
@@ -114,7 +108,6 @@
/**
* Windowing mode that was requested last via {@link android.window.WindowContainerTransaction}.
*/
- // TODO(b/289875940): review this and other field that might need to be moved in the base class.
@WindowingMode
private int mLastRequestedWindowingMode = WINDOWING_MODE_UNDEFINED;
@@ -443,7 +436,7 @@
// Remove the activity now because there can be a delay before the server callback.
mInfo.getActivities().remove(activityToken);
}
- mActivitiesToFinishOnExit.remove(activityToken);
+ mParcelableData.mActivitiesToFinishOnExit.remove(activityToken);
finishSelfWithActivityIfNeeded(wct, activityToken);
}
@@ -624,7 +617,20 @@
if (mIsFinished) {
return;
}
- mActivitiesToFinishOnExit.add(activityToFinish.getActivityToken());
+ mParcelableData.mActivitiesToFinishOnExit.add(activityToFinish.getActivityToken());
+ }
+
+ /**
+ * Returns {@code true} if an Activity from the given {@code container} was added to be
+ * finished on exit. Otherwise, return {@code false}.
+ */
+ boolean hasActivityToFinishOnExit(@NonNull TaskFragmentContainer container) {
+ for (IBinder activity : mParcelableData.mActivitiesToFinishOnExit) {
+ if (container.hasActivity(activity)) {
+ return true;
+ }
+ }
+ return false;
}
/**
@@ -634,7 +640,7 @@
if (mIsFinished) {
return;
}
- mActivitiesToFinishOnExit.remove(activityToRemove.getActivityToken());
+ mParcelableData.mActivitiesToFinishOnExit.remove(activityToRemove.getActivityToken());
}
/** Removes all dependencies that should be finished when this container is finished. */
@@ -643,7 +649,7 @@
return;
}
mContainersToFinishOnExit.clear();
- mActivitiesToFinishOnExit.clear();
+ mParcelableData.mActivitiesToFinishOnExit.clear();
}
/**
@@ -721,7 +727,7 @@
mContainersToFinishOnExit.clear();
// Finish associated activities
- for (IBinder activityToken : mActivitiesToFinishOnExit) {
+ for (IBinder activityToken : mParcelableData.mActivitiesToFinishOnExit) {
final Activity activity = mController.getActivity(activityToken);
if (activity == null || activity.isFinishing()
|| controller.shouldRetainAssociatedActivity(this, activity)) {
@@ -729,7 +735,7 @@
}
wct.finishActivity(activity.getActivityToken());
}
- mActivitiesToFinishOnExit.clear();
+ mParcelableData.mActivitiesToFinishOnExit.clear();
}
@GuardedBy("mController.mLock")
@@ -1082,7 +1088,7 @@
+ " pendingAppearedActivities=" + mPendingAppearedActivities
+ (includeContainersToFinishOnExit ? " containersToFinishOnExit="
+ containersToFinishOnExitToString() : "")
- + " activitiesToFinishOnExit=" + mActivitiesToFinishOnExit
+ + " activitiesToFinishOnExit=" + mParcelableData.mActivitiesToFinishOnExit
+ " info=" + mInfo
+ "}";
}
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 249e9a2..7078d66 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -661,4 +661,7 @@
<dimen name="desktop_windowing_education_promo_height">352dp</dimen>
<!-- The corner radius of the desktop windowing education promo. -->
<dimen name="desktop_windowing_education_promo_corner_radius">28dp</dimen>
+
+ <!-- The corner radius of freeform tasks in desktop windowing. -->
+ <dimen name="desktop_windowing_freeform_rounded_corner_radius">16dp</dimen>
</resources>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt
index 23e7441..f14dfdb 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt
@@ -50,6 +50,22 @@
fun createMenu(snapshotList: List<Pair<Int, TaskSnapshot>>,
onIconClickListener: ((Int) -> Unit),
onOutsideClickListener: (() -> Unit)): ManageWindowsView {
+ val bitmapList = snapshotList.map { (index, snapshot) ->
+ index to Bitmap.wrapHardwareBuffer(snapshot.hardwareBuffer, snapshot.colorSpace)
+ }
+ return createAndShowMenuView(
+ bitmapList,
+ onIconClickListener,
+ onOutsideClickListener
+ )
+ }
+
+ /** Creates the menu view with the given bitmaps, and displays it. */
+ fun createAndShowMenuView(
+ snapshotList: List<Pair<Int, Bitmap?>>,
+ onIconClickListener: ((Int) -> Unit),
+ onOutsideClickListener: (() -> Unit)
+ ): ManageWindowsView {
menuView = ManageWindowsView(context, menuBackgroundColor).apply {
this.onOutsideClickListener = onOutsideClickListener
this.onIconClickListener = onIconClickListener
@@ -120,7 +136,7 @@
}
fun generateIconViews(
- snapshotList: List<Pair<Int, TaskSnapshot>>
+ snapshotList: List<Pair<Int, Bitmap?>>
) {
menuWidth = 0
menuHeight = 0
@@ -133,7 +149,7 @@
// Add each icon to the menu, adding a new row when needed.
for ((iconCount, taskInfoSnapshotPair) in snapshotList.withIndex()) {
val taskId = taskInfoSnapshotPair.first
- val snapshot = taskInfoSnapshotPair.second
+ val snapshotBitmap = taskInfoSnapshotPair.second
// Once a row is filled, make a new row and increase the menu height.
if (iconCount % MENU_MAX_ICONS_PER_ROW == 0) {
rowLayout = LinearLayout(context)
@@ -141,10 +157,7 @@
rootView.addView(rowLayout)
menuHeight += (instanceIconHeight + iconMargin).toInt()
}
- val snapshotBitmap = Bitmap.wrapHardwareBuffer(
- snapshot.hardwareBuffer,
- snapshot.colorSpace
- )
+
val croppedBitmap = snapshotBitmap?.let { cropBitmap(it) }
val scaledSnapshotBitmap = croppedBitmap?.let {
Bitmap.createScaledBitmap(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index f532be6..12d20bf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -96,6 +96,14 @@
}
/**
+ * Get all the displays from DisplayManager.
+ */
+ public Display[] getDisplays() {
+ final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+ return displayManager.getDisplays();
+ }
+
+ /**
* Gets the DisplayLayout associated with a display.
*/
public @Nullable DisplayLayout getDisplayLayout(int displayId) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxConfiguration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxConfiguration.kt
new file mode 100644
index 0000000..3bcbf13
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxConfiguration.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox
+
+import android.annotation.ColorRes
+import android.content.Context
+import android.graphics.Color
+import com.android.internal.R
+import com.android.wm.shell.dagger.WMSingleton
+import javax.inject.Inject
+
+/**
+ * Contains configuration properties for the letterbox implementation in Shell.
+ */
+@WMSingleton
+class LetterboxConfiguration @Inject constructor(
+ private val context: Context
+) {
+
+ // TODO(b/370940685): Integrate CommandHandler in LetterboxConfiguration.
+ private var letterboxBackgroundColorOverride: Color? = Color.valueOf(Color.YELLOW)
+
+ // Color resource id for the solid color letterbox background type.
+ private var letterboxBackgroundColorResourceIdOverride: Int? = null
+
+ /**
+ * Sets color of letterbox background which is used when using the solid background mode.
+ */
+ fun setLetterboxBackgroundColor(color: Color) {
+ letterboxBackgroundColorOverride = color
+ }
+
+ /**
+ * Sets color ID of letterbox background which is used when using the solid background mode.
+ */
+ fun setLetterboxBackgroundColorResourceId(@ColorRes colorId: Int) {
+ letterboxBackgroundColorResourceIdOverride = colorId
+ }
+
+ /**
+ * Gets color of letterbox background which is used when the solid color mode is active.
+ */
+ fun getLetterboxBackgroundColor(): Color {
+ if (letterboxBackgroundColorOverride != null) {
+ return letterboxBackgroundColorOverride!!
+ }
+ val colorId = if (letterboxBackgroundColorResourceIdOverride != null) {
+ letterboxBackgroundColorResourceIdOverride
+ } else {
+ R.color.config_letterboxBackgroundColor
+ }
+ // Query color dynamically because material colors extracted from wallpaper are updated
+ // when wallpaper is changed.
+ return Color.valueOf(context.getResources().getColor(colorId!!, /* theme */null))
+ }
+
+ /**
+ * Resets color of letterbox background to the default.
+ */
+ fun resetLetterboxBackgroundColor() {
+ letterboxBackgroundColorOverride = null
+ letterboxBackgroundColorResourceIdOverride = null
+ }
+
+ /**
+ * The background color for the Letterbox.
+ */
+ fun getBackgroundColorRgbArray(): FloatArray = getLetterboxBackgroundColor().components
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt
new file mode 100644
index 0000000..0ac7aff
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox
+
+import android.graphics.Rect
+import android.view.SurfaceControl
+import com.android.internal.protolog.ProtoLog
+import com.android.wm.shell.dagger.WMSingleton
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_APP_COMPAT
+import javax.inject.Inject
+
+/**
+ * Component responsible for handling the lifecycle of the letterbox surfaces.
+ */
+@WMSingleton
+class LetterboxController @Inject constructor(
+ private val letterboxConfiguration: LetterboxConfiguration
+) {
+
+ companion object {
+ /*
+ * Letterbox surfaces need to stay below the activity layer which is 0.
+ */
+ // TODO(b/378673153): Consider adding this to [TaskConstants].
+ @JvmStatic
+ private val TASK_CHILD_LAYER_LETTERBOX_BACKGROUND = -1000
+ @JvmStatic
+ private val TAG = "LetterboxController"
+ }
+
+ private val letterboxMap = mutableMapOf<LetterboxKey, LetterboxItem>()
+
+ /**
+ * Creates a Letterbox Surface for a given displayId/taskId if it doesn't exist.
+ */
+ fun createLetterboxSurface(
+ key: LetterboxKey,
+ startTransaction: SurfaceControl.Transaction,
+ parentLeash: SurfaceControl
+ ) {
+ letterboxMap.runOnItem(key, onMissed = { k, m ->
+ m[k] = LetterboxItem(
+ SurfaceControl.Builder()
+ .setName("ShellLetterboxSurface-$key")
+ .setHidden(true)
+ .setColorLayer()
+ .setParent(parentLeash)
+ .setCallsite("LetterboxController-createLetterboxSurface")
+ .build().apply {
+ startTransaction.setLayer(
+ this,
+ TASK_CHILD_LAYER_LETTERBOX_BACKGROUND
+ ).setColorSpaceAgnostic(this, true)
+ .setColor(this, letterboxConfiguration.getBackgroundColorRgbArray())
+ }
+ )
+ })
+ }
+
+ /**
+ * Invoked to destroy the surfaces for a letterbox session for given displayId/taskId.
+ */
+ fun destroyLetterboxSurface(
+ key: LetterboxKey,
+ startTransaction: SurfaceControl.Transaction
+ ) {
+ letterboxMap.runOnItem(key, onFound = { item ->
+ item.fullWindowSurface?.run {
+ startTransaction.remove(this)
+ }
+ })
+ letterboxMap.remove(key)
+ }
+
+ /**
+ * Invoked to show/hide the letterbox surfaces for given displayId/taskId.
+ */
+ fun updateLetterboxSurfaceVisibility(
+ key: LetterboxKey,
+ startTransaction: SurfaceControl.Transaction,
+ visible: Boolean = true
+ ) {
+ letterboxMap.runOnItem(key, onFound = { item ->
+ item.fullWindowSurface?.run {
+ startTransaction.setVisibility(this, visible)
+ }
+ })
+ }
+
+ /**
+ * Updates the bounds for the letterbox surfaces for given displayId/taskId.
+ */
+ fun updateLetterboxSurfaceBounds(
+ key: LetterboxKey,
+ startTransaction: SurfaceControl.Transaction,
+ bounds: Rect
+ ) {
+ letterboxMap.runOnItem(key, onFound = { item ->
+ item.fullWindowSurface?.run {
+ startTransaction.moveAndCrop(this, bounds)
+ }
+ })
+ }
+
+ /*
+ * Executes [onFound] on the [LetterboxItem] if present or [onMissed] if not present.
+ */
+ private fun MutableMap<LetterboxKey, LetterboxItem>.runOnItem(
+ key: LetterboxKey,
+ onFound: (LetterboxItem) -> Unit = { _ -> },
+ onMissed: (
+ LetterboxKey,
+ MutableMap<LetterboxKey, LetterboxItem>
+ ) -> Unit = { _, _ -> }
+ ) {
+ this[key]?.let {
+ return onFound(it)
+ }
+ return onMissed(key, this)
+ }
+
+ fun dump() {
+ ProtoLog.v(WM_SHELL_APP_COMPAT, "%s: %s", TAG, "${letterboxMap.keys}")
+ }
+
+ private fun SurfaceControl.Transaction.moveAndCrop(
+ surface: SurfaceControl,
+ rect: Rect
+ ): SurfaceControl.Transaction =
+ setPosition(surface, rect.left.toFloat(), rect.top.toFloat())
+ .setWindowCrop(
+ surface,
+ rect.width(),
+ rect.height()
+ )
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt
new file mode 100644
index 0000000..98fd247
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox
+
+import android.view.SurfaceControl
+
+// The key to use for identify the letterbox sessions.
+data class LetterboxKey(val displayId: Int, val taskId: Int)
+
+// Encapsulate the objects for the specific letterbox session.
+data class LetterboxItem(val fullWindowSurface: SurfaceControl?)
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt
new file mode 100644
index 0000000..67429bd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox
+
+import android.graphics.Rect
+import android.os.IBinder
+import android.view.SurfaceControl
+import android.window.TransitionInfo
+import com.android.internal.protolog.ProtoLog
+import com.android.window.flags.Flags.appCompatRefactoring
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_APP_COMPAT
+import com.android.wm.shell.shared.TransitionUtil.isClosingType
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+
+/**
+ * The [TransitionObserver] to handle Letterboxing events in Shell.
+ */
+class LetterboxTransitionObserver(
+ shellInit: ShellInit,
+ private val transitions: Transitions,
+ private val letterboxController: LetterboxController
+) : Transitions.TransitionObserver {
+
+ companion object {
+ @JvmStatic
+ private val TAG = "LetterboxTransitionObserver"
+ }
+
+ init {
+ if (appCompatRefactoring()) {
+ ProtoLog.v(
+ WM_SHELL_APP_COMPAT,
+ "%s: %s",
+ TAG,
+ "Initializing LetterboxTransitionObserver"
+ )
+ shellInit.addInitCallback({
+ transitions.registerObserver(this)
+ }, this)
+ }
+ }
+
+ override fun onTransitionReady(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction
+ ) {
+ // We recognise the operation to execute and delegate to the LetterboxController
+ // the related operation.
+ // TODO(b/377875151): Identify Desktop Windowing Transactions.
+ // TODO(b/377857898): Handling multiple surfaces
+ // TODO(b/371500295): Handle input events detection.
+ for (change in info.changes) {
+ change.taskInfo?.let { ti ->
+ val key = LetterboxKey(ti.displayId, ti.taskId)
+ if (isClosingType(change.mode)) {
+ letterboxController.destroyLetterboxSurface(
+ key,
+ startTransaction
+ )
+ } else {
+ val isTopActivityLetterboxed = ti.appCompatTaskInfo.isTopActivityLetterboxed
+ if (isTopActivityLetterboxed) {
+ letterboxController.createLetterboxSurface(
+ key,
+ startTransaction,
+ change.leash
+ )
+ letterboxController.updateLetterboxSurfaceBounds(
+ key,
+ startTransaction,
+ Rect(
+ change.endRelOffset.x,
+ change.endRelOffset.y,
+ change.endAbsBounds.width(),
+ change.endAbsBounds.height()
+ )
+ )
+ }
+ letterboxController.updateLetterboxSurfaceVisibility(
+ key,
+ startTransaction,
+ isTopActivityLetterboxed
+ )
+ }
+ letterboxController.dump()
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index e455985..02df38e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -795,13 +795,14 @@
static KeyguardTransitionHandler provideKeyguardTransitionHandler(
ShellInit shellInit,
ShellController shellController,
+ DisplayController displayController,
Transitions transitions,
TaskStackListenerImpl taskStackListener,
@ShellMainThread Handler mainHandler,
@ShellMainThread ShellExecutor mainExecutor) {
return new KeyguardTransitionHandler(
- shellInit, shellController, transitions, taskStackListener, mainHandler,
- mainExecutor);
+ shellInit, shellController, displayController, transitions, taskStackListener,
+ mainHandler, mainExecutor);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 601cf70..307e12e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -20,6 +20,7 @@
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT;
import static android.window.DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.KeyguardManager;
import android.content.Context;
@@ -62,6 +63,8 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.compatui.letterbox.LetterboxController;
+import com.android.wm.shell.compatui.letterbox.LetterboxTransitionObserver;
import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
import com.android.wm.shell.dagger.pip.PipModule;
import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler;
@@ -1229,8 +1232,23 @@
@Provides
static Object provideIndependentShellComponentsToCreate(
DragAndDropController dragAndDropController,
+ @NonNull LetterboxTransitionObserver letterboxTransitionObserver,
Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional,
Optional<DesktopDisplayEventHandler> desktopDisplayEventHandler) {
return new Object();
}
+
+ //
+ // App Compat
+ //
+
+ @WMSingleton
+ @Provides
+ static LetterboxTransitionObserver provideLetterboxTransitionObserver(
+ @NonNull ShellInit shellInit,
+ @NonNull Transitions transitions,
+ @NonNull LetterboxController letterboxController
+ ) {
+ return new LetterboxTransitionObserver(shellInit, transitions, letterboxController);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index e848b88..2ae9828 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -254,8 +254,13 @@
finishT.hide(sc);
final Rect startBounds = new Rect(change.getStartAbsBounds());
animator.addUpdateListener(animation -> {
- t.setPosition(sc, startBounds.left,
- startBounds.top + (animation.getAnimatedFraction() * screenHeight));
+ final float newTop = startBounds.top + (animation.getAnimatedFraction() * screenHeight);
+ t.setPosition(sc, startBounds.left, newTop);
+ if (newTop > screenHeight) {
+ // At this point the task surface is off-screen, so hide it to prevent flicker
+ // failures. See b/377651666.
+ t.hide(sc);
+ }
t.apply();
});
animator.addListener(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index b618bf1..319bfac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -42,6 +42,7 @@
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
+import android.view.Display;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.IRemoteTransition;
@@ -54,6 +55,7 @@
import com.android.internal.protolog.ProtoLog;
import com.android.window.flags.Flags;
+import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
@@ -80,6 +82,8 @@
private final Transitions mTransitions;
private final ShellController mShellController;
+
+ private final DisplayController mDisplayController;
private final Handler mMainHandler;
private final ShellExecutor mMainExecutor;
@@ -121,12 +125,14 @@
public KeyguardTransitionHandler(
@NonNull ShellInit shellInit,
@NonNull ShellController shellController,
+ @NonNull DisplayController displayController,
@NonNull Transitions transitions,
@NonNull TaskStackListenerImpl taskStackListener,
@NonNull Handler mainHandler,
@NonNull ShellExecutor mainExecutor) {
mTransitions = transitions;
mShellController = shellController;
+ mDisplayController = displayController;
mMainHandler = mainHandler;
mMainExecutor = mainExecutor;
mTaskStackListener = taskStackListener;
@@ -429,10 +435,10 @@
@Override
public void startKeyguardTransition(boolean keyguardShowing, boolean aodShowing) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- final KeyguardState keyguardState =
- new KeyguardState.Builder(android.view.Display.DEFAULT_DISPLAY)
- .setKeyguardShowing(keyguardShowing).setAodShowing(aodShowing).build();
- wct.addKeyguardState(keyguardState);
+ for (Display display : mDisplayController.getDisplays()) {
+ wct.addKeyguardState(new KeyguardState.Builder(display.getDisplayId())
+ .setKeyguardShowing(keyguardShowing).setAodShowing(aodShowing).build());
+ }
mMainExecutor.execute(() -> {
mTransitions.startTransition(keyguardShowing ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK,
wct, KeyguardTransitionHandler.this);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
index eb33ff4..35c90ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
@@ -52,6 +52,9 @@
private final SurfaceControl.Transaction mStartTransaction;
private final SurfaceControl.Transaction mFinishTransaction;
+ private final int mCornerRadius;
+ private final int mShadowRadius;
+
// Bounds updated by the evaluator as animator is running.
private final Rect mAnimatedRect = new Rect();
@@ -128,6 +131,8 @@
final int enterAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipEnterAnimationDuration);
+ mCornerRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius);
+ mShadowRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_shadow_radius);
setDuration(enterAnimationDuration);
setFloatValues(0f, 1f);
setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
@@ -177,6 +182,8 @@
mTransformTensor.postRotate(degrees);
tx.setMatrix(mLeash, mTransformTensor, mMatrixTmp);
+ tx.setCornerRadius(mLeash, mCornerRadius).setShadowRadius(mLeash, mShadowRadius);
+
if (mContentOverlay != null) {
mContentOverlay.onAnimationUpdate(tx, 1f / scaleX, fraction, mEndBounds);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
index 4558a9f..06e8349 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
@@ -29,6 +29,7 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.R;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.shared.animation.Interpolators;
@@ -50,6 +51,9 @@
private Runnable mAnimationEndCallback;
private RectEvaluator mRectEvaluator;
+ private final int mCornerRadius;
+ private final int mShadowRadius;
+
// Bounds relative to which scaling/cropping must be done.
private final Rect mBaseBounds = new Rect();
@@ -74,7 +78,8 @@
mAnimationStartCallback.run();
}
if (mStartTx != null) {
- setBoundsAndRotation(mStartTx, mLeash, mBaseBounds, mStartBounds, mDelta);
+ setBoundsAndRotation(mStartTx, mLeash, mBaseBounds, mStartBounds, mDelta,
+ mCornerRadius, mShadowRadius);
mStartTx.apply();
}
}
@@ -83,7 +88,8 @@
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (mFinishTx != null) {
- setBoundsAndRotation(mFinishTx, mLeash, mBaseBounds, mEndBounds, 0f);
+ setBoundsAndRotation(mFinishTx, mLeash, mBaseBounds, mEndBounds, 0f,
+ mCornerRadius, mShadowRadius);
}
if (mAnimationEndCallback != null) {
mAnimationEndCallback.run();
@@ -99,7 +105,8 @@
mSurfaceControlTransactionFactory.getTransaction();
final float fraction = getAnimatedFraction();
final float degrees = (1.0f - fraction) * mDelta;
- setBoundsAndRotation(tx, mLeash, mBaseBounds, mAnimatedRect, degrees);
+ setBoundsAndRotation(tx, mLeash, mBaseBounds, mAnimatedRect, degrees,
+ mCornerRadius, mShadowRadius);
tx.apply();
}
};
@@ -128,6 +135,9 @@
mRectEvaluator = new RectEvaluator(mAnimatedRect);
+ mCornerRadius = mContext.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius);
+ mShadowRadius = mContext.getResources().getDimensionPixelSize(R.dimen.pip_shadow_radius);
+
setObjectValues(startBounds, endBounds);
setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
addListener(mAnimatorListener);
@@ -152,7 +162,7 @@
* @param degrees degrees of rotation - counter-clockwise is positive by convention.
*/
private static void setBoundsAndRotation(SurfaceControl.Transaction tx, SurfaceControl leash,
- Rect baseBounds, Rect targetBounds, float degrees) {
+ Rect baseBounds, Rect targetBounds, float degrees, int cornerRadius, int shadowRadius) {
Matrix transformTensor = new Matrix();
final float[] mMatrixTmp = new float[9];
final float scaleX = (float) targetBounds.width() / baseBounds.width();
@@ -162,7 +172,9 @@
transformTensor.postTranslate(targetBounds.left, targetBounds.top);
transformTensor.postRotate(degrees, targetBounds.centerX(), targetBounds.centerY());
- tx.setMatrix(leash, transformTensor, mMatrixTmp);
+ tx.setMatrix(leash, transformTensor, mMatrixTmp)
+ .setCornerRadius(leash, cornerRadius)
+ .setShadowRadius(leash, shadowRadius);
}
@VisibleForTesting
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index e901c39..6d2df95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.pip2.phone;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
@@ -293,6 +294,11 @@
// Update the display layout caches even if we are not in PiP.
setDisplayLayout(mDisplayController.getDisplayLayout(displayId));
+ if (toRotation != ROTATION_UNDEFINED) {
+ // Make sure we rotate to final rotation ourselves in case display change is coming
+ // from the remote rotation as a part of an already collecting transition.
+ mPipDisplayLayoutState.rotateTo(toRotation);
+ }
if (!mPipTransitionState.isInPip()) {
// Skip the PiP-relevant updates if we aren't in a valid PiP state.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index 3738353..fd387d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -785,8 +785,16 @@
private void handleFlingTransition(SurfaceControl.Transaction startTx,
SurfaceControl.Transaction finishTx, Rect destinationBounds) {
- startTx.setPosition(mPipTransitionState.getPinnedTaskLeash(),
- destinationBounds.left, destinationBounds.top);
+ SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash();
+ int cornerRadius = mContext.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius);
+ int shadowRadius = mContext.getResources().getDimensionPixelSize(R.dimen.pip_shadow_radius);
+
+ // merge transactions so everything is done on startTx
+ startTx.merge(finishTx);
+
+ startTx.setPosition(pipLeash, destinationBounds.left, destinationBounds.top)
+ .setCornerRadius(pipLeash, cornerRadius)
+ .setShadowRadius(pipLeash, shadowRadius);
startTx.apply();
// All motion operations have actually finished, so make bounds cache updates.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index d415c10..08e6727 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -241,7 +241,7 @@
extra.putParcelable(PIP_TASK_LEASH, pipChange.getLeash());
mPipTransitionState.setState(PipTransitionState.ENTERING_PIP, extra);
- if (mPipTransitionState.isInSwipePipToHomeTransition()) {
+ if (isInSwipePipToHomeTransition()) {
// If this is the second transition as a part of swipe PiP to home cuj,
// handle this transition as a special case with no-op animation.
return handleSwipePipToHomeTransition(info, startTransaction, finishTransaction,
@@ -702,6 +702,13 @@
@NonNull TransitionInfo.Change pipChange) {
TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
int startRotation = pipChange.getStartRotation();
+ if (pipChange.getEndRotation() != ROTATION_UNDEFINED
+ && startRotation != pipChange.getEndRotation()) {
+ // If PiP change was collected along with the display change and the orientation change
+ // happened in sync with the PiP change, then do not treat this as fixed-rotation case.
+ return ROTATION_0;
+ }
+
int endRotation = fixedRotationChange != null
? fixedRotationChange.getEndFixedRotation() : mPipDisplayLayoutState.getRotation();
int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
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 cdcf14e..9cb9d25 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
@@ -81,7 +81,6 @@
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.IconProvider;
import com.android.window.flags.Flags;
@@ -1008,8 +1007,10 @@
relayoutParams.mWindowDecorConfig = windowDecorConfig;
if (DesktopModeStatus.useRoundedCorners()) {
- relayoutParams.mCornerRadius =
- (int) ScreenDecorationsUtils.getWindowCornerRadius(context);
+ relayoutParams.mCornerRadius = taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+ ? loadDimensionPixelSize(context.getResources(),
+ R.dimen.desktop_windowing_freeform_rounded_corner_radius)
+ : INVALID_CORNER_RADIUS;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index a3c75bf..852eee5f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -107,6 +107,11 @@
static final int INPUT_SINK_Z_ORDER = -2;
/**
+ * Invalid corner radius that signifies that corner radius should not be set.
+ */
+ static final int INVALID_CORNER_RADIUS = -1;
+
+ /**
* System-wide context. Only used to create context with overridden configurations.
*/
final Context mContext;
@@ -449,20 +454,22 @@
startT.show(mTaskSurface);
}
- if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
- if (!DesktopModeStatus.isVeiledResizeEnabled()) {
- // When fluid resize is enabled, add a background to freeform tasks
- int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
- mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
- mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
- mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
- startT.setColor(mTaskSurface, mTmpColor);
- }
- startT.setCornerRadius(mTaskSurface, params.mCornerRadius);
- finishT.setCornerRadius(mTaskSurface, params.mCornerRadius);
+ if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+ && !DesktopModeStatus.isVeiledResizeEnabled()) {
+ // When fluid resize is enabled, add a background to freeform tasks
+ int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
+ mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
+ mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
+ mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
+ startT.setColor(mTaskSurface, mTmpColor);
} else if (!DesktopModeStatus.isVeiledResizeEnabled()) {
startT.unsetColor(mTaskSurface);
}
+
+ if (params.mCornerRadius != INVALID_CORNER_RADIUS) {
+ startT.setCornerRadius(mTaskSurface, params.mCornerRadius);
+ finishT.setCornerRadius(mTaskSurface, params.mCornerRadius);
+ }
}
/**
@@ -593,13 +600,25 @@
private Rect calculateBoundingRectLocal(@NonNull OccludingCaptionElement element,
int elementWidthPx, @NonNull Rect captionRect) {
+ final boolean isRtl =
+ mDecorWindowContext.getResources().getConfiguration().getLayoutDirection()
+ == View.LAYOUT_DIRECTION_RTL;
switch (element.mAlignment) {
case START -> {
- return new Rect(0, 0, elementWidthPx, captionRect.height());
+ if (isRtl) {
+ return new Rect(captionRect.width() - elementWidthPx, 0,
+ captionRect.width(), captionRect.height());
+ } else {
+ return new Rect(0, 0, elementWidthPx, captionRect.height());
+ }
}
case END -> {
- return new Rect(captionRect.width() - elementWidthPx, 0,
- captionRect.width(), captionRect.height());
+ if (isRtl) {
+ return new Rect(0, 0, elementWidthPx, captionRect.height());
+ } else {
+ return new Rect(captionRect.width() - elementWidthPx, 0,
+ captionRect.width(), captionRect.height());
+ }
}
}
throw new IllegalArgumentException("Unexpected alignment " + element.mAlignment);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
index 503ad92..5f25f42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
@@ -114,7 +114,7 @@
// If handle is not in status bar region(i.e., bottom stage in vertical split),
// do not create an input layer
if (position.y >= SystemBarUtils.getStatusBarHeight(context)) return
- if (!isCaptionVisible && statusBarInputLayerExists) {
+ if (!isCaptionVisible) {
disposeStatusBarInputLayer()
return
}
@@ -227,6 +227,7 @@
* is not visible.
*/
fun disposeStatusBarInputLayer() {
+ if (!statusBarInputLayerExists) return
statusBarInputLayerExists = false
handler.post {
statusBarInputLayer?.releaseView()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/BringDesktopAppsToFrontLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/BringDesktopAppsToFrontLandscape.kt
new file mode 100644
index 0000000..898964f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/BringDesktopAppsToFrontLandscape.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.wm.shell.flicker
+
+import android.tools.Rotation.ROTATION_90
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.BRING_APPS_TO_FRONT
+import com.android.wm.shell.scenarios.BringDesktopAppsToFront
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Bring apps to front by clicking on the App Header.
+ *
+ * Assert that the app windows move to front.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class BringDesktopAppsToFrontLandscape : BringDesktopAppsToFront(rotation = ROTATION_90) {
+
+ @ExpectedScenarios(["BRING_APPS_TO_FRONT"])
+ @Test
+ override fun bringDesktopAppsToFront() = super.bringDesktopAppsToFront()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig()
+ .use(FlickerServiceConfig.DEFAULT)
+ .use(BRING_APPS_TO_FRONT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/BringDesktopAppsToFrontPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/BringDesktopAppsToFrontPortrait.kt
new file mode 100644
index 0000000..b123d7d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/BringDesktopAppsToFrontPortrait.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.wm.shell.flicker
+
+import android.tools.Rotation.ROTATION_0
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.BRING_APPS_TO_FRONT
+import com.android.wm.shell.scenarios.BringDesktopAppsToFront
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Bring apps to front by clicking on the App Header.
+ *
+ * Assert that the app windows move to front.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class BringDesktopAppsToFrontPortrait : BringDesktopAppsToFront(rotation = ROTATION_0) {
+
+ @ExpectedScenarios(["BRING_APPS_TO_FRONT"])
+ @Test
+ override fun bringDesktopAppsToFront() = super.bringDesktopAppsToFront()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig()
+ .use(FlickerServiceConfig.DEFAULT)
+ .use(BRING_APPS_TO_FRONT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
index 4cddf31..88dc548 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
@@ -25,6 +25,7 @@
import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAtStart
import android.tools.flicker.assertors.assertions.AppWindowAlignsWithOnlyOneDisplayCornerAtEnd
import android.tools.flicker.assertors.assertions.AppWindowBecomesInvisible
+import android.tools.flicker.assertors.assertions.AppWindowBecomesTopWindow
import android.tools.flicker.assertors.assertions.AppWindowBecomesVisible
import android.tools.flicker.assertors.assertions.AppWindowCoversLeftHalfScreenAtEnd
import android.tools.flicker.assertors.assertions.AppWindowCoversRightHalfScreenAtEnd
@@ -345,6 +346,30 @@
).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
+ val BRING_APPS_TO_FRONT =
+ FlickerConfigEntry(
+ scenarioId = ScenarioId("BRING_APPS_TO_FRONT"),
+ extractor =
+ ShellTransitionScenarioExtractor(
+ transitionMatcher =
+ object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> {
+ return transitions.filter {
+ it.type == TransitionType.TO_FRONT
+ }
+ }
+ }
+ ),
+ assertions =
+ AssertionTemplates.COMMON_ASSERTIONS +
+ listOf(
+ AppWindowBecomesTopWindow(DESKTOP_MODE_APP),
+ AppWindowOnTopAtEnd(DESKTOP_MODE_APP),
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING })
+ )
+
val CASCADE_APP =
FlickerConfigEntry(
scenarioId = ScenarioId("CASCADE_APP"),
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/BringDesktopAppsToFrontTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/BringDesktopAppsToFrontTest.kt
new file mode 100644
index 0000000..6c8cc68
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/BringDesktopAppsToFrontTest.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.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.BringDesktopAppsToFront
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/** Functional test for [BringDesktopAppsToFront]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class BringDesktopAppsToFrontTest : BringDesktopAppsToFront()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/BringDesktopAppsToFront.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/BringDesktopAppsToFront.kt
new file mode 100644
index 0000000..1db22eb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/BringDesktopAppsToFront.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.wm.shell.scenarios
+
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.MailAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Test Base Class")
+abstract class BringDesktopAppsToFront(
+ val rotation: Rotation = Rotation.ROTATION_0,
+ isResizable: Boolean = true,
+ isLandscapeApp: Boolean = true,
+) : DesktopScenarioCustomAppTestBase(isResizable, isLandscapeApp) {
+
+ private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation))
+
+ @Rule
+ @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+ ChangeDisplayOrientationRule.setRotation(rotation)
+ tapl.enableTransientTaskbar(false)
+ // Launch a first app and snap it to left side so that it doesn't overlap too much with
+ // the next launching app, and their headers are visible enough to switch focus by tapping
+ // on them.
+ testApp.enterDesktopMode(wmHelper, device)
+ testApp.snapResizeDesktopApp(wmHelper, device, instrumentation.context, toLeft = true)
+ mailApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun bringDesktopAppsToFront() {
+ testApp.bringToFront(wmHelper, device)
+ mailApp.bringToFront(wmHelper, device)
+ testApp.bringToFront(wmHelper, device)
+ mailApp.bringToFront(wmHelper, device)
+ }
+
+ @After
+ fun teardown() {
+ mailApp.exit(wmHelper)
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxConfigurationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxConfigurationTest.kt
new file mode 100644
index 0000000..75025d90
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxConfigurationTest.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox
+
+import android.annotation.ColorRes
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Color
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
+import com.android.internal.R
+import com.android.wm.shell.ShellTestCase
+import java.util.function.Consumer
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+
+/**
+ * Tests for [LetterboxConfiguration].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:LetterboxConfigurationTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LetterboxConfigurationTest : ShellTestCase() {
+
+ companion object {
+ @JvmStatic
+ val COLOR_WHITE = Color.valueOf(Color.WHITE)
+ @JvmStatic
+ val COLOR_RED = Color.valueOf(Color.RED)
+ @JvmStatic
+ val COLOR_BLACK = Color.valueOf(Color.BLACK)
+ @JvmStatic
+ val COLOR_WHITE_RESOURCE_ID = android.R.color.white
+ @JvmStatic
+ val COLOR_BLACK_RESOURCE_ID = android.R.color.black
+ }
+
+ @Test
+ fun `default background color is used if override is not set`() {
+ runTestScenario { r ->
+ r.setDefaultBackgroundColorId(COLOR_WHITE_RESOURCE_ID)
+ r.loadConfiguration()
+ r.checkBackgroundColor(COLOR_WHITE)
+ }
+ }
+
+ @Test
+ fun `overridden background color is used if set`() {
+ runTestScenario { r ->
+ r.setDefaultBackgroundColorId(COLOR_WHITE_RESOURCE_ID)
+ r.loadConfiguration()
+ r.overrideBackgroundColor(COLOR_RED)
+ r.checkBackgroundColor(COLOR_RED)
+ }
+ }
+
+ @Test
+ fun `overridden background color resource is used if set without override`() {
+ runTestScenario { r ->
+ r.setDefaultBackgroundColorId(COLOR_WHITE_RESOURCE_ID)
+ r.loadConfiguration()
+ r.overrideBackgroundColorId(COLOR_BLACK_RESOURCE_ID)
+ r.checkBackgroundColor(COLOR_BLACK)
+ }
+ }
+
+ @Test
+ fun `overridden background color has precedence over color id`() {
+ runTestScenario { r ->
+ r.setDefaultBackgroundColorId(COLOR_WHITE_RESOURCE_ID)
+ r.loadConfiguration()
+ r.overrideBackgroundColor(COLOR_RED)
+ r.overrideBackgroundColorId(COLOR_BLACK_RESOURCE_ID)
+ r.checkBackgroundColor(COLOR_RED)
+ }
+ }
+
+ @Test
+ fun `reset background color`() {
+ runTestScenario { r ->
+ r.setDefaultBackgroundColorId(COLOR_WHITE_RESOURCE_ID)
+ r.loadConfiguration()
+ r.overrideBackgroundColor(COLOR_RED)
+ r.checkBackgroundColor(COLOR_RED)
+
+ r.resetBackgroundColor()
+ r.checkBackgroundColor(COLOR_WHITE)
+
+ r.overrideBackgroundColorId(COLOR_BLACK_RESOURCE_ID)
+ r.checkBackgroundColor(COLOR_BLACK)
+
+ r.resetBackgroundColor()
+ r.checkBackgroundColor(COLOR_WHITE)
+ }
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ fun runTestScenario(consumer: Consumer<LetterboxConfigurationRobotTest>) {
+ val robot = LetterboxConfigurationRobotTest(mContext)
+ consumer.accept(robot)
+ }
+
+ class LetterboxConfigurationRobotTest(private val ctx: Context) {
+
+ private val resources: Resources
+ private lateinit var letterboxConfig: LetterboxConfiguration
+
+ init {
+ resources = ctx.resources
+ spyOn(resources)
+ }
+
+ fun setDefaultBackgroundColorId(@ColorRes colorId: Int) {
+ doReturn(colorId).`when`(resources)
+ .getColor(R.color.config_letterboxBackgroundColor, null)
+ }
+
+ fun loadConfiguration() {
+ letterboxConfig = LetterboxConfiguration(ctx)
+ }
+
+ fun overrideBackgroundColor(color: Color) {
+ letterboxConfig.setLetterboxBackgroundColor(color)
+ }
+
+ fun resetBackgroundColor() {
+ letterboxConfig.resetLetterboxBackgroundColor()
+ }
+
+ fun overrideBackgroundColorId(@ColorRes colorId: Int) {
+ letterboxConfig.setLetterboxBackgroundColorResourceId(colorId)
+ }
+
+ fun checkBackgroundColor(expected: Color) {
+ val colorComponents = letterboxConfig.getBackgroundColorRgbArray()
+ val expectedComponents = expected.components
+ assert(expectedComponents.contentEquals(colorComponents))
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt
new file mode 100644
index 0000000..1ae1c3f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox
+
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.testing.AndroidTestingRunner
+import android.view.WindowManager.TRANSIT_CLOSE
+import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.util.TransitionObserverInputBuilder
+import com.android.wm.shell.util.executeTransitionObserverTest
+import java.util.function.Consumer
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.anyOrNull
+import org.mockito.verification.VerificationMode
+
+/**
+ * Tests for [LetterboxTransitionObserver].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:LetterboxTransitionObserverTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LetterboxTransitionObserverTest : ShellTestCase() {
+
+ @get:Rule
+ val setFlagsRule: SetFlagsRule = SetFlagsRule()
+
+ @Test
+ @DisableFlags(Flags.FLAG_APP_COMPAT_REFACTORING)
+ fun `when initialized and flag disabled the observer is not registered`() {
+ runTestScenario { r ->
+ executeTransitionObserverTest(observerFactory = r.observerFactory) {
+ r.invokeShellInit()
+ r.checkObservableIsRegistered(expected = false)
+ }
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_APP_COMPAT_REFACTORING)
+ fun `when initialized and flag enabled the observer is registered`() {
+ runTestScenario { r ->
+ executeTransitionObserverTest(observerFactory = r.observerFactory) {
+ r.invokeShellInit()
+ r.checkObservableIsRegistered(expected = true)
+ }
+ }
+ }
+
+ @Test
+ fun `LetterboxController not used without TaskInfos in Change`() {
+ runTestScenario { r ->
+ executeTransitionObserverTest(observerFactory = r.observerFactory) {
+ r.invokeShellInit()
+
+ inputBuilder {
+ buildTransitionInfo()
+ addChange(createChange())
+ addChange(createChange())
+ addChange(createChange())
+ }
+
+ validateOutput {
+ r.creationEventDetected(expected = false)
+ r.visibilityEventDetected(expected = false)
+ r.destroyEventDetected(expected = false)
+ r.boundsEventDetected(expected = false)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `When a topActivity is letterboxed surfaces creation is requested`() {
+ runTestScenario { r ->
+ executeTransitionObserverTest(observerFactory = r.observerFactory) {
+ r.invokeShellInit()
+
+ inputBuilder {
+ buildTransitionInfo()
+ r.createTopActivityChange(inputBuilder = this, isLetterboxed = true)
+ }
+
+ validateOutput {
+ r.creationEventDetected(expected = true)
+ r.visibilityEventDetected(expected = true, visible = true)
+ r.destroyEventDetected(expected = false)
+ r.boundsEventDetected(expected = true)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `When a topActivity is not letterboxed visibility is updated`() {
+ runTestScenario { r ->
+ executeTransitionObserverTest(observerFactory = r.observerFactory) {
+ r.invokeShellInit()
+
+ inputBuilder {
+ buildTransitionInfo()
+ r.createTopActivityChange(inputBuilder = this, isLetterboxed = false)
+ }
+
+ validateOutput {
+ r.creationEventDetected(expected = false)
+ r.visibilityEventDetected(expected = true, visible = false)
+ r.destroyEventDetected(expected = false)
+ r.boundsEventDetected(expected = false)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `When closing change letterbox surface destroy is triggered`() {
+ runTestScenario { r ->
+ executeTransitionObserverTest(observerFactory = r.observerFactory) {
+ r.invokeShellInit()
+
+ inputBuilder {
+ buildTransitionInfo()
+ r.createClosingChange(inputBuilder = this)
+ }
+
+ validateOutput {
+ r.destroyEventDetected(expected = true)
+ r.creationEventDetected(expected = false)
+ r.visibilityEventDetected(expected = false, visible = false)
+ r.boundsEventDetected(expected = false)
+ }
+ }
+ }
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ fun runTestScenario(consumer: Consumer<LetterboxTransitionObserverRobotTest>) {
+ val robot = LetterboxTransitionObserverRobotTest()
+ consumer.accept(robot)
+ }
+
+ class LetterboxTransitionObserverRobotTest {
+
+ companion object {
+ @JvmStatic
+ private val DISPLAY_ID = 1
+
+ @JvmStatic
+ private val TASK_ID = 20
+ }
+
+ private val executor: ShellExecutor
+ private val shellInit: ShellInit
+ private val transitions: Transitions
+ private val letterboxController: LetterboxController
+ private val letterboxObserver: LetterboxTransitionObserver
+
+ val observerFactory: () -> LetterboxTransitionObserver
+
+ init {
+ executor = Mockito.mock(ShellExecutor::class.java)
+ shellInit = ShellInit(executor)
+ transitions = Mockito.mock(Transitions::class.java)
+ letterboxController = Mockito.mock(LetterboxController::class.java)
+ letterboxObserver =
+ LetterboxTransitionObserver(shellInit, transitions, letterboxController)
+ observerFactory = { letterboxObserver }
+ }
+
+ fun invokeShellInit() = shellInit.init()
+
+ fun observer() = letterboxObserver
+
+ fun checkObservableIsRegistered(expected: Boolean) {
+ Mockito.verify(transitions, expected.asMode()).registerObserver(observer())
+ }
+
+ fun creationEventDetected(
+ expected: Boolean,
+ displayId: Int = DISPLAY_ID,
+ taskId: Int = TASK_ID
+ ) {
+ Mockito.verify(letterboxController, expected.asMode()).createLetterboxSurface(
+ toLetterboxKeyMatcher(displayId, taskId),
+ anyOrNull(),
+ anyOrNull()
+ )
+ }
+
+ fun visibilityEventDetected(
+ expected: Boolean,
+ displayId: Int = DISPLAY_ID,
+ taskId: Int = TASK_ID,
+ visible: Boolean? = null
+ ) {
+ Mockito.verify(letterboxController, expected.asMode()).updateLetterboxSurfaceVisibility(
+ toLetterboxKeyMatcher(displayId, taskId),
+ anyOrNull(),
+ visible.asMatcher()
+ )
+ }
+
+ fun destroyEventDetected(
+ expected: Boolean,
+ displayId: Int = DISPLAY_ID,
+ taskId: Int = TASK_ID
+ ) {
+ Mockito.verify(letterboxController, expected.asMode()).destroyLetterboxSurface(
+ toLetterboxKeyMatcher(displayId, taskId),
+ anyOrNull()
+ )
+ }
+
+ fun boundsEventDetected(
+ expected: Boolean,
+ displayId: Int = DISPLAY_ID,
+ taskId: Int = TASK_ID
+ ) {
+ Mockito.verify(letterboxController, expected.asMode()).updateLetterboxSurfaceBounds(
+ toLetterboxKeyMatcher(displayId, taskId),
+ anyOrNull(),
+ anyOrNull()
+ )
+ }
+
+ fun createTopActivityChange(
+ inputBuilder: TransitionObserverInputBuilder,
+ isLetterboxed: Boolean = true,
+ displayId: Int = DISPLAY_ID,
+ taskId: Int = TASK_ID
+ ) {
+ inputBuilder.addChange(changeTaskInfo = inputBuilder.createTaskInfo().apply {
+ appCompatTaskInfo.isTopActivityLetterboxed = isLetterboxed
+ this.taskId = taskId
+ this.displayId = displayId
+ })
+ }
+
+ fun createClosingChange(
+ inputBuilder: TransitionObserverInputBuilder,
+ displayId: Int = DISPLAY_ID,
+ taskId: Int = TASK_ID
+ ) {
+ inputBuilder.addChange(changeTaskInfo = inputBuilder.createTaskInfo().apply {
+ this.taskId = taskId
+ this.displayId = displayId
+ }, changeMode = TRANSIT_CLOSE)
+ }
+
+ private fun Boolean.asMode(): VerificationMode = if (this) times(1) else never()
+
+ private fun Boolean?.asMatcher(): Boolean =
+ if (this != null) eq(this) else any()
+
+ private fun toLetterboxKeyMatcher(displayId: Int, taskId: Int): LetterboxKey {
+ if (displayId < 0 || taskId < 0) {
+ return any()
+ } else {
+ return eq(LetterboxKey(displayId, taskId))
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
index a4008c1..72c4666 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
@@ -22,6 +22,7 @@
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
@@ -39,6 +40,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.wm.shell.R;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip2.phone.PipAppIconOverlay;
@@ -49,33 +51,25 @@
import org.mockito.MockitoAnnotations;
/**
- * Unit test again {@link PipEnterAnimator}.
+ * Unit test against {@link PipEnterAnimator}.
*/
@SmallTest
@TestableLooper.RunWithLooper
@RunWith(AndroidTestingRunner.class)
public class PipEnterAnimatorTest {
+ private static final float TEST_CORNER_RADIUS = 1f;
+ private static final float TEST_SHADOW_RADIUS = 2f;
@Mock private Context mMockContext;
-
@Mock private Resources mMockResources;
-
@Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
-
@Mock private SurfaceControl.Transaction mMockAnimateTransaction;
-
@Mock private SurfaceControl.Transaction mMockStartTransaction;
-
@Mock private SurfaceControl.Transaction mMockFinishTransaction;
-
@Mock private Runnable mMockStartCallback;
-
@Mock private Runnable mMockEndCallback;
-
@Mock private PipAppIconOverlay mMockPipAppIconOverlay;
-
@Mock private SurfaceControl mMockAppIconOverlayLeash;
-
@Mock private ActivityInfo mMockActivityInfo;
@Surface.Rotation private int mRotation;
@@ -89,13 +83,15 @@
when(mMockContext.getResources()).thenReturn(mMockResources);
when(mMockResources.getInteger(anyInt())).thenReturn(0);
when(mMockFactory.getTransaction()).thenReturn(mMockAnimateTransaction);
- when(mMockAnimateTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
- .thenReturn(mMockAnimateTransaction);
- when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
- .thenReturn(mMockStartTransaction);
- when(mMockFinishTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
- .thenReturn(mMockFinishTransaction);
when(mMockPipAppIconOverlay.getLeash()).thenReturn(mMockAppIconOverlayLeash);
+ when(mMockResources.getDimensionPixelSize(R.dimen.pip_corner_radius))
+ .thenReturn((int) TEST_CORNER_RADIUS);
+ when(mMockResources.getDimensionPixelSize(R.dimen.pip_shadow_radius))
+ .thenReturn((int) TEST_SHADOW_RADIUS);
+
+ prepareTransaction(mMockAnimateTransaction);
+ prepareTransaction(mMockStartTransaction);
+ prepareTransaction(mMockFinishTransaction);
mTestLeash = new SurfaceControl.Builder()
.setContainerLayer()
@@ -122,6 +118,12 @@
verify(mMockStartCallback).run();
verifyZeroInteractions(mMockEndCallback);
+
+ // Check corner and shadow radii were set
+ verify(mMockAnimateTransaction, atLeastOnce())
+ .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+ verify(mMockAnimateTransaction, atLeastOnce())
+ .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
}
@Test
@@ -142,6 +144,12 @@
verify(mMockStartCallback).run();
verify(mMockEndCallback).run();
+
+ // Check corner and shadow radii were set
+ verify(mMockAnimateTransaction, atLeastOnce())
+ .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+ verify(mMockAnimateTransaction, atLeastOnce())
+ .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
}
@Test
@@ -197,5 +205,21 @@
verify(mMockPipAppIconOverlay).onAnimationUpdate(
eq(mMockAnimateTransaction), anyFloat(), eq(fraction), eq(mEndBounds));
+
+ // Check corner and shadow radii were set
+ verify(mMockAnimateTransaction, atLeastOnce())
+ .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+ verify(mMockAnimateTransaction, atLeastOnce())
+ .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
+ }
+
+ // set up transaction chaining
+ private void prepareTransaction(SurfaceControl.Transaction tx) {
+ when(tx.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+ .thenReturn(tx);
+ when(tx.setCornerRadius(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(tx);
+ when(tx.setShadowRadius(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(tx);
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java
index 0adb50b..23fbad0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java
@@ -16,15 +16,18 @@
package com.android.wm.shell.pip2.animation;
+import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.mockito.kotlin.MatchersKt.eq;
-import static org.junit.Assert.assertEquals;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
@@ -34,12 +37,14 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.wm.shell.R;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -52,40 +57,40 @@
public class PipResizeAnimatorTest {
private static final float FLOAT_COMPARISON_DELTA = 0.001f;
+ private static final float TEST_CORNER_RADIUS = 1f;
+ private static final float TEST_SHADOW_RADIUS = 2f;
@Mock private Context mMockContext;
-
+ @Mock private Resources mMockResources;
@Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
-
@Mock private SurfaceControl.Transaction mMockTransaction;
-
@Mock private SurfaceControl.Transaction mMockStartTransaction;
-
@Mock private SurfaceControl.Transaction mMockFinishTransaction;
-
@Mock private Runnable mMockStartCallback;
-
@Mock private Runnable mMockEndCallback;
+ @Captor private ArgumentCaptor<Matrix> mArgumentCaptor;
+
private PipResizeAnimator mPipResizeAnimator;
private Rect mBaseBounds;
private Rect mStartBounds;
private Rect mEndBounds;
private SurfaceControl mTestLeash;
- private ArgumentCaptor<Matrix> mArgumentCaptor;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mMockFactory.getTransaction()).thenReturn(mMockTransaction);
- when(mMockTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
- .thenReturn(mMockTransaction);
- when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
- .thenReturn(mMockStartTransaction);
- when(mMockFinishTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
- .thenReturn(mMockFinishTransaction);
+ when(mMockContext.getResources()).thenReturn(mMockResources);
+ when(mMockResources.getDimensionPixelSize(R.dimen.pip_corner_radius))
+ .thenReturn((int) TEST_CORNER_RADIUS);
+ when(mMockResources.getDimensionPixelSize(R.dimen.pip_shadow_radius))
+ .thenReturn((int) TEST_SHADOW_RADIUS);
- mArgumentCaptor = ArgumentCaptor.forClass(Matrix.class);
+ prepareTransaction(mMockTransaction);
+ prepareTransaction(mMockStartTransaction);
+ prepareTransaction(mMockFinishTransaction);
+
mTestLeash = new SurfaceControl.Builder()
.setContainerLayer()
.setName("PipResizeAnimatorTest")
@@ -187,6 +192,12 @@
assertEquals(matrix[Matrix.MSCALE_Y], 1f, FLOAT_COMPARISON_DELTA);
assertEquals(matrix[Matrix.MTRANS_X], mEndBounds.left, FLOAT_COMPARISON_DELTA);
assertEquals(matrix[Matrix.MTRANS_Y], mEndBounds.top, FLOAT_COMPARISON_DELTA);
+
+ // Check corner and shadow radii were set
+ verify(mMockTransaction, atLeastOnce())
+ .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+ verify(mMockTransaction, atLeastOnce())
+ .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
}
@Test
@@ -237,6 +248,12 @@
assertEquals(matrix[Matrix.MSCALE_Y], 1f, FLOAT_COMPARISON_DELTA);
assertEquals(matrix[Matrix.MTRANS_X], mEndBounds.left, FLOAT_COMPARISON_DELTA);
assertEquals(matrix[Matrix.MTRANS_Y], mEndBounds.top, FLOAT_COMPARISON_DELTA);
+
+ // Check corner and shadow radii were set
+ verify(mMockTransaction, atLeastOnce())
+ .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+ verify(mMockTransaction, atLeastOnce())
+ .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
}
@Test
@@ -272,5 +289,21 @@
mArgumentCaptor.getValue().getValues(matrix);
assertEquals(matrix[Matrix.MSKEW_X], 0f, FLOAT_COMPARISON_DELTA);
assertEquals(matrix[Matrix.MSKEW_Y], 0f, FLOAT_COMPARISON_DELTA);
+
+ // Check corner and shadow radii were set
+ verify(mMockTransaction, atLeastOnce())
+ .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+ verify(mMockTransaction, atLeastOnce())
+ .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
+ }
+
+ // set up transaction chaining
+ private void prepareTransaction(SurfaceControl.Transaction tx) {
+ when(tx.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+ .thenReturn(tx);
+ when(tx.setCornerRadius(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(tx);
+ when(tx.setShadowRadius(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(tx);
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt
new file mode 100644
index 0000000..3e26ee0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.util
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.content.ComponentName
+import android.content.Intent
+import android.os.IBinder
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import android.view.WindowManager.TRANSIT_NONE
+import android.view.WindowManager.TransitionFlags
+import android.view.WindowManager.TransitionType
+import android.window.IWindowContainerToken
+import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
+import android.window.TransitionInfo.ChangeFlags
+import android.window.TransitionInfo.FLAG_NONE
+import android.window.TransitionInfo.TransitionMode
+import android.window.WindowContainerToken
+import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
+import com.android.wm.shell.transition.Transitions.TransitionObserver
+import org.mockito.Mockito
+import org.mockito.kotlin.mock
+
+@DslMarker
+annotation class TransitionObserverTagMarker
+
+/**
+ * Abstraction for all the phases of the [TransitionObserver] test.
+ */
+interface TransitionObserverTestStep
+
+/**
+ * Encapsulates the values for the [TransitionObserver#onTransitionReady] input parameters.
+ */
+class TransitionObserverTransitionReadyInput(
+ val transition: IBinder,
+ val info: TransitionInfo,
+ val startTransaction: Transaction,
+ val finishTransaction: Transaction
+)
+
+@TransitionObserverTagMarker
+class TransitionObserverTestContext : TransitionObserverTestStep {
+
+ lateinit var transitionObserver: TransitionObserver
+ lateinit var transitionReadyInput: TransitionObserverTransitionReadyInput
+
+ fun inputBuilder(builderInput: TransitionObserverInputBuilder.() -> Unit) {
+ val inputFactoryObj = TransitionObserverInputBuilder()
+ inputFactoryObj.builderInput()
+ transitionReadyInput = inputFactoryObj.build()
+ }
+
+ fun validateOutput(
+ validate:
+ TransitionObserverResultValidation.() -> Unit
+ ) {
+ val validateObj = TransitionObserverResultValidation()
+ invokeObservable()
+ validateObj.validate()
+ }
+
+ fun invokeObservable() {
+ transitionObserver.onTransitionReady(
+ transitionReadyInput.transition,
+ transitionReadyInput.info,
+ transitionReadyInput.startTransaction,
+ transitionReadyInput.finishTransaction
+ )
+ }
+}
+
+/**
+ * Phase responsible for the input parameters for [TransitionObserver].
+ */
+class TransitionObserverInputBuilder : TransitionObserverTestStep {
+
+ private val transition = Mockito.mock(IBinder::class.java)
+ private var transitionInfo: TransitionInfo? = null
+ private val startTransaction = Mockito.mock(Transaction::class.java)
+ private val finishTransaction = Mockito.mock(Transaction::class.java)
+
+ fun buildTransitionInfo(
+ @TransitionType type: Int = TRANSIT_NONE,
+ @TransitionFlags flags: Int = 0
+ ) {
+ transitionInfo = TransitionInfo(type, flags)
+ spyOn(transitionInfo)
+ }
+
+ fun addChange(
+ token: WindowContainerToken? = mock(),
+ leash: SurfaceControl = mock(),
+ @TransitionMode changeMode: Int = TRANSIT_NONE,
+ parentToken: WindowContainerToken? = null,
+ changeTaskInfo: RunningTaskInfo? = null,
+ @ChangeFlags changeFlags: Int = FLAG_NONE
+ ) = addChange(Change(token, leash).apply {
+ mode = changeMode
+ parent = parentToken
+ taskInfo = changeTaskInfo
+ flags = changeFlags
+ })
+
+ fun createChange(
+ token: WindowContainerToken? = mock(),
+ leash: SurfaceControl = mock(),
+ @TransitionMode changeMode: Int = TRANSIT_NONE,
+ parentToken: WindowContainerToken? = null,
+ changeTaskInfo: RunningTaskInfo? = null,
+ @ChangeFlags changeFlags: Int = FLAG_NONE
+ ) = Change(token, leash).apply {
+ mode = changeMode
+ parent = parentToken
+ taskInfo = changeTaskInfo
+ flags = changeFlags
+ }
+
+ fun addChange(change: Change) {
+ transitionInfo!!.addChange(change)
+ }
+
+ fun createTaskInfo(id: Int = 0, windowingMode: Int = WINDOWING_MODE_FREEFORM) =
+ RunningTaskInfo().apply {
+ taskId = id
+ displayId = DEFAULT_DISPLAY
+ configuration.windowConfiguration.windowingMode = windowingMode
+ token = WindowContainerToken(Mockito.mock(IWindowContainerToken::class.java))
+ baseIntent = Intent().apply {
+ component = ComponentName("package", "component.name")
+ }
+ }
+
+ fun build(): TransitionObserverTransitionReadyInput = TransitionObserverTransitionReadyInput(
+ transition = transition,
+ info = transitionInfo!!,
+ startTransaction = startTransaction,
+ finishTransaction = finishTransaction
+ )
+}
+
+/**
+ * Phase responsible for the execution of validation methods.
+ */
+class TransitionObserverResultValidation : TransitionObserverTestStep
+
+/**
+ * Allows to run a test about a specific [TransitionObserver] passing the specific
+ * implementation and input value as parameters for the [TransitionObserver#onTransitionReady]
+ * method.
+ * @param observerFactory The Factory for the TransitionObserver
+ * @param inputFactory The Builder for the onTransitionReady input parameters
+ * @param init The test code itself.
+ */
+fun executeTransitionObserverTest(
+ observerFactory: () -> TransitionObserver,
+ init: TransitionObserverTestContext.() -> Unit
+): TransitionObserverTestContext {
+ val testContext = TransitionObserverTestContext().apply {
+ transitionObserver = observerFactory()
+ }
+ testContext.init()
+ return testContext
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 1d2d0f0..f653622 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -30,6 +30,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlTransaction;
import static com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.CLOSE_MAXIMIZE_MENU_DELAY_MS;
+import static com.android.wm.shell.windowdecor.WindowDecoration.INVALID_CORNER_RADIUS;
import static com.google.common.truth.Truth.assertThat;
@@ -219,7 +220,7 @@
@Captor
private ArgumentCaptor<Runnable> mCloseMaxMenuRunnable;
- private final InsetsState mInsetsState = new InsetsState();
+ private final InsetsState mInsetsState = createInsetsState(statusBars(), /* visible= */true);
private SurfaceControl.Transaction mMockTransaction;
private StaticMockitoSession mMockitoSession;
private TestableContext mTestableContext;
@@ -312,8 +313,9 @@
}
@Test
- public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersAreEnabled() {
+ public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersSetForFreeform() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
fillRoundedCornersResources(/* fillValue= */ 30);
RelayoutParams relayoutParams = new RelayoutParams();
@@ -334,6 +336,29 @@
}
@Test
+ public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersNotSetForFullscreen() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ fillRoundedCornersResources(/* fillValue= */ 30);
+ RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false,
+ /* isStatusBarVisible */ true,
+ /* isKeyguardVisibleAndOccluded */ false,
+ /* inFullImmersiveMode */ false,
+ new InsetsState(),
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
+
+ assertThat(relayoutParams.mCornerRadius).isEqualTo(INVALID_CORNER_RADIUS);
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY)
public void updateRelayoutParams_appHeader_usesTaskDensity() {
final int systemDensity = mTestableContext.getOrCreateTestableResources().getResources()
@@ -1408,8 +1433,6 @@
public void notifyCaptionStateChanged_flagDisabled_doNoNotify() {
when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
- when(mMockDisplayController.getInsetsState(taskInfo.displayId))
- .thenReturn(createInsetsState(statusBars(), /* visible= */true));
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -1423,8 +1446,6 @@
public void notifyCaptionStateChanged_inFullscreenMode_notifiesAppHandleVisible() {
when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
- when(mMockDisplayController.getInsetsState(taskInfo.displayId))
- .thenReturn(createInsetsState(statusBars(), /* visible= */true));
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
@@ -1444,8 +1465,6 @@
public void notifyCaptionStateChanged_inWindowingMode_notifiesAppHeaderVisible() {
when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
- when(mMockDisplayController.getInsetsState(taskInfo.displayId))
- .thenReturn(createInsetsState(statusBars(), /* visible= */true));
when(mMockAppHeaderViewHolder.getAppChipLocationInWindow()).thenReturn(
new Rect(/* left= */ 0, /* top= */ 1, /* right= */ 2, /* bottom= */ 3));
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -1473,8 +1492,6 @@
public void notifyCaptionStateChanged_taskNotVisible_notifiesNoCaptionVisible() {
when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ false);
- when(mMockDisplayController.getInsetsState(taskInfo.displayId))
- .thenReturn(createInsetsState(statusBars(), /* visible= */true));
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
@@ -1493,8 +1510,6 @@
public void notifyCaptionStateChanged_captionHandleExpanded_notifiesHandleMenuExpanded() {
when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
- when(mMockDisplayController.getInsetsState(taskInfo.displayId))
- .thenReturn(createInsetsState(statusBars(), /* visible= */true));
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
@@ -1518,8 +1533,6 @@
public void notifyCaptionStateChanged_captionHandleClosed_notifiesHandleMenuClosed() {
when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
- when(mMockDisplayController.getInsetsState(taskInfo.displayId))
- .thenReturn(createInsetsState(statusBars(), /* visible= */true));
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
diff --git a/libs/hwui/jni/RuntimeEffectUtils.cpp b/libs/hwui/jni/RuntimeEffectUtils.cpp
index 46db863..ad0e540 100644
--- a/libs/hwui/jni/RuntimeEffectUtils.cpp
+++ b/libs/hwui/jni/RuntimeEffectUtils.cpp
@@ -90,7 +90,7 @@
SkFlattenable* childEffect) {
SkRuntimeShaderBuilder::BuilderChild builderChild = builder->child(childName);
if (builderChild.fChild == nullptr) {
- ThrowIAEFmt(env, "unable to find shader named %s", childName);
+ ThrowIAEFmt(env, "unable to find child named %s", childName);
return;
}
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 2a057e7..018c2b13 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -2,6 +2,7 @@
#include "Gainmap.h"
#include "GraphicsJNI.h"
+#include "RuntimeEffectUtils.h"
#include "SkBitmap.h"
#include "SkBlendMode.h"
#include "SkColor.h"
@@ -280,50 +281,6 @@
return ret;
}
-static bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) {
- switch (type) {
- case SkRuntimeEffect::Uniform::Type::kFloat:
- case SkRuntimeEffect::Uniform::Type::kFloat2:
- case SkRuntimeEffect::Uniform::Type::kFloat3:
- case SkRuntimeEffect::Uniform::Type::kFloat4:
- case SkRuntimeEffect::Uniform::Type::kFloat2x2:
- case SkRuntimeEffect::Uniform::Type::kFloat3x3:
- case SkRuntimeEffect::Uniform::Type::kFloat4x4:
- return false;
- case SkRuntimeEffect::Uniform::Type::kInt:
- case SkRuntimeEffect::Uniform::Type::kInt2:
- case SkRuntimeEffect::Uniform::Type::kInt3:
- case SkRuntimeEffect::Uniform::Type::kInt4:
- return true;
- }
-}
-
-static void UpdateFloatUniforms(JNIEnv* env, SkRuntimeShaderBuilder* builder,
- const char* uniformName, const float values[], int count,
- bool isColor) {
- SkRuntimeShaderBuilder::BuilderUniform uniform = builder->uniform(uniformName);
- if (uniform.fVar == nullptr) {
- ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
- } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) {
- if (isColor) {
- jniThrowExceptionFmt(
- env, "java/lang/IllegalArgumentException",
- "attempting to set a color uniform using the non-color specific APIs: %s %x",
- uniformName, uniform.fVar->flags);
- } else {
- ThrowIAEFmt(env,
- "attempting to set a non-color uniform using the setColorUniform APIs: %s",
- uniformName);
- }
- } else if (isIntUniformType(uniform.fVar->type)) {
- ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s",
- uniformName);
- } else if (!uniform.set<float>(values, count)) {
- ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
- uniform.fVar->sizeInBytes(), sizeof(float) * count);
- }
-}
-
static void RuntimeShader_updateFloatUniforms(JNIEnv* env, jobject, jlong shaderBuilder,
jstring jUniformName, jfloat value1, jfloat value2,
jfloat value3, jfloat value4, jint count) {
@@ -342,20 +299,6 @@
UpdateFloatUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length(), isColor);
}
-static void UpdateIntUniforms(JNIEnv* env, SkRuntimeShaderBuilder* builder, const char* uniformName,
- const int values[], int count) {
- SkRuntimeShaderBuilder::BuilderUniform uniform = builder->uniform(uniformName);
- if (uniform.fVar == nullptr) {
- ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
- } else if (!isIntUniformType(uniform.fVar->type)) {
- ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s",
- uniformName);
- } else if (!uniform.set<int>(values, count)) {
- ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
- uniform.fVar->sizeInBytes(), sizeof(float) * count);
- }
-}
-
static void RuntimeShader_updateIntUniforms(JNIEnv* env, jobject, jlong shaderBuilder,
jstring jUniformName, jint value1, jint value2,
jint value3, jint value4, jint count) {
@@ -388,6 +331,15 @@
builder->child(name.c_str()) = sk_ref_sp(shader);
}
+static void RuntimeShader_updateChild(JNIEnv* env, jobject, jlong shaderBuilder,
+ jstring jUniformName, jlong childHandle) {
+ SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
+ ScopedUtfChars name(env, jUniformName);
+ auto* childEffect = reinterpret_cast<SkFlattenable*>(childHandle);
+
+ UpdateChild(env, builder, name.c_str(), childEffect);
+}
+
///////////////////////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gShaderMethods[] = {
@@ -428,6 +380,7 @@
{"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V",
(void*)RuntimeShader_updateIntUniforms},
{"nativeUpdateShader", "(JLjava/lang/String;J)V", (void*)RuntimeShader_updateShader},
+ {"nativeUpdateChild", "(JLjava/lang/String;J)V", (void*)RuntimeShader_updateChild},
};
int register_android_graphics_Shader(JNIEnv* env)
diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java
index 9325999..0a79f41 100644
--- a/media/java/android/media/ImageWriter.java
+++ b/media/java/android/media/ImageWriter.java
@@ -1157,7 +1157,11 @@
@Override
public void setFence(@NonNull SyncFence fence) throws IOException {
throwISEIfImageIsInvalid();
- nativeSetFenceFd(fence.getFdDup().detachFd());
+ if (fence.isValid()) {
+ nativeSetFenceFd(fence.getFdDup().detachFd());
+ } else {
+ nativeSetFenceFd(-1);
+ }
}
@Override
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 5b1ea8b..d8a8c8b 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -1,13 +1,7 @@
package: "com.android.media.flags"
container: "system"
-flag {
- name: "enable_rlp_callbacks_in_media_router2"
- is_exported: true
- namespace: "media_solutions"
- description: "Make RouteListingPreference getter and callbacks public in MediaRouter2."
- bug: "281067101"
-}
+# Flags are ordered alphabetically by name.
flag {
name: "adjust_volume_for_foreground_app_playing_audio_without_media_session"
@@ -17,6 +11,13 @@
}
flag {
+ name: "enable_audio_input_device_routing_and_volume_control"
+ namespace: "media_better_together"
+ description: "Allows audio input devices routing and volume control via system settings."
+ bug: "355684672"
+}
+
+flag {
name: "enable_audio_policies_device_and_bluetooth_controller"
is_exported: true
namespace: "media_solutions"
@@ -25,17 +26,54 @@
}
flag {
- name: "fallback_to_default_handling_when_media_session_has_fixed_volume_handling"
- namespace: "media_solutions"
- description: "Fallbacks to the default handling for volume adjustment when media session has fixed volume handling and its app is in the foreground and setting a media controller."
- bug: "293743975"
+ name: "enable_built_in_speaker_route_suitability_statuses"
+ is_exported: true
+ namespace: "media_solutions"
+ description: "Make MediaRoute2Info provide information about routes suitability for transfer."
+ bug: "279555229"
}
flag {
- name: "enable_waiting_state_for_system_session_creation_request"
+ name: "enable_cross_user_routing_in_media_router2"
+ is_exported: true
namespace: "media_solutions"
- description: "Introduces a waiting state for the session creation request and prevents it from early failing when the selectedRoute from the bluetooth stack doesn't match the pending request route id."
- bug: "307723189"
+ description: "Allows clients of privileged MediaRouter2 that hold INTERACT_ACROSS_USERS_FULL to control routing across users."
+ bug: "288580225"
+}
+
+flag {
+ name: "enable_full_scan_with_media_content_control"
+ namespace: "media_better_together"
+ description: "Allows holders of the MEDIA_CONTENT_CONTROL permission to scan for routes while not in the foreground."
+ bug: "352401364"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enable_get_transferable_routes"
+ is_exported: true
+ namespace: "media_solutions"
+ description: "Exposes RoutingController#getTransferableRoutes() (previously hidden) to the public API."
+ bug: "323154573"
+}
+
+flag {
+ name: "enable_mirroring_in_media_router_2"
+ namespace: "media_better_together"
+ description: "Enables support for mirroring routes in the MediaRouter2 framework, allowing Output Switcher to offer mirroring routes."
+ bug: "362507305"
+}
+
+flag {
+ name: "enable_mr2_service_non_main_bg_thread"
+ namespace: "media_solutions"
+ description: "Enables the use of a background thread in the media routing framework, instead of using the main thread."
+ bug: "310145678"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
@@ -55,37 +93,6 @@
}
flag {
- name: "enable_privileged_routing_for_media_routing_control"
- is_exported: true
- namespace: "media_solutions"
- description: "Allow access to privileged routing capabilities to MEDIA_ROUTING_CONTROL holders."
- bug: "305919655"
-}
-
-flag {
- name: "enable_cross_user_routing_in_media_router2"
- is_exported: true
- namespace: "media_solutions"
- description: "Allows clients of privileged MediaRouter2 that hold INTERACT_ACROSS_USERS_FULL to control routing across users."
- bug: "288580225"
-}
-
-flag {
- name: "enable_use_of_bluetooth_device_get_alias_for_mr2info_get_name"
- namespace: "media_solutions"
- description: "Use BluetoothDevice.getAlias to populate the name of Bluetooth MediaRoute2Infos."
- bug: "314324170"
-}
-
-flag {
- name: "enable_built_in_speaker_route_suitability_statuses"
- is_exported: true
- namespace: "media_solutions"
- description: "Make MediaRoute2Info provide information about routes suitability for transfer."
- bug: "279555229"
-}
-
-flag {
name: "enable_notifying_activity_manager_with_media_session_status_change"
is_exported: true
namespace: "media_solutions"
@@ -94,11 +101,10 @@
}
flag {
- name: "enable_get_transferable_routes"
- is_exported: true
+ name: "enable_null_session_in_media_browser_service"
namespace: "media_solutions"
- description: "Exposes RoutingController#getTransferableRoutes() (previously hidden) to the public API."
- bug: "323154573"
+ description: "Enables apps owning a MediaBrowserService to disconnect all connected browsers."
+ bug: "185136506"
}
flag {
@@ -109,31 +115,6 @@
}
flag {
- name: "enable_mr2_service_non_main_bg_thread"
- namespace: "media_solutions"
- description: "Enables the use of a background thread in the media routing framework, instead of using the main thread."
- bug: "310145678"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
- name: "enable_screen_off_scanning"
- is_exported: true
- namespace: "media_solutions"
- description: "Enable new MediaRouter2 API to enable watch companion apps to scan while the phone screen is off."
- bug: "281072508"
-}
-
-flag {
- name: "enable_null_session_in_media_browser_service"
- namespace: "media_solutions"
- description: "Enables apps owning a MediaBrowserService to disconnect all connected browsers."
- bug: "185136506"
-}
-
-flag {
name: "enable_prevention_of_manager_scans_when_no_apps_scan"
namespace: "media_solutions"
description: "Prevents waking up route providers when no apps are scanning, even if SysUI or Settings are scanning."
@@ -144,25 +125,46 @@
}
flag {
- name: "enable_full_scan_with_media_content_control"
- namespace: "media_better_together"
- description: "Allows holders of the MEDIA_CONTENT_CONTROL permission to scan for routes while not in the foreground."
- bug: "352401364"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
+ name: "enable_privileged_routing_for_media_routing_control"
+ is_exported: true
+ namespace: "media_solutions"
+ description: "Allow access to privileged routing capabilities to MEDIA_ROUTING_CONTROL holders."
+ bug: "305919655"
}
flag {
- name: "enable_audio_input_device_routing_and_volume_control"
- namespace: "media_better_together"
- description: "Allows audio input devices routing and volume control via system settings."
- bug: "355684672"
+ name: "enable_rlp_callbacks_in_media_router2"
+ is_exported: true
+ namespace: "media_solutions"
+ description: "Make RouteListingPreference getter and callbacks public in MediaRouter2."
+ bug: "281067101"
}
flag {
- name: "enable_mirroring_in_media_router_2"
- namespace: "media_better_together"
- description: "Enables support for mirroring routes in the MediaRouter2 framework, allowing Output Switcher to offer mirroring routes."
- bug: "362507305"
+ name: "enable_screen_off_scanning"
+ is_exported: true
+ namespace: "media_solutions"
+ description: "Enable new MediaRouter2 API to enable watch companion apps to scan while the phone screen is off."
+ bug: "281072508"
+}
+
+flag {
+ name: "enable_use_of_bluetooth_device_get_alias_for_mr2info_get_name"
+ namespace: "media_solutions"
+ description: "Use BluetoothDevice.getAlias to populate the name of Bluetooth MediaRoute2Infos."
+ bug: "314324170"
+}
+
+flag {
+ name: "enable_waiting_state_for_system_session_creation_request"
+ namespace: "media_solutions"
+ description: "Introduces a waiting state for the session creation request and prevents it from early failing when the selectedRoute from the bluetooth stack doesn't match the pending request route id."
+ bug: "307723189"
+}
+
+flag {
+ name: "fallback_to_default_handling_when_media_session_has_fixed_volume_handling"
+ namespace: "media_solutions"
+ description: "Fallbacks to the default handling for volume adjustment when media session has fixed volume handling and its app is in the foreground and setting a media controller."
+ bug: "293743975"
}
diff --git a/media/java/android/media/quality/AmbientBacklightEvent.java b/media/java/android/media/quality/AmbientBacklightEvent.java
index 5c11def..273f21e 100644
--- a/media/java/android/media/quality/AmbientBacklightEvent.java
+++ b/media/java/android/media/quality/AmbientBacklightEvent.java
@@ -40,7 +40,7 @@
@IntDef({AMBIENT_BACKLIGHT_EVENT_ENABLED, AMBIENT_BACKLIGHT_EVENT_DISABLED,
AMBIENT_BACKLIGHT_EVENT_METADATA,
AMBIENT_BACKLIGHT_EVENT_INTERRUPTED})
- public @interface AmbientBacklightEventTypes {}
+ public @interface Type {}
/**
* Event type for ambient backlight events. The ambient backlight is enabled.
@@ -69,9 +69,9 @@
private final AmbientBacklightMetadata mMetadata;
/**
- * Constructor of AmbientBacklightEvent.
+ * Constructs AmbientBacklightEvent.
*/
- public AmbientBacklightEvent(int eventType,
+ public AmbientBacklightEvent(@Type int eventType,
@Nullable AmbientBacklightMetadata metadata) {
mEventType = eventType;
mMetadata = metadata;
@@ -85,6 +85,7 @@
/**
* Gets event type.
*/
+ @Type
public int getEventType() {
return mEventType;
}
diff --git a/media/java/android/media/quality/AmbientBacklightMetadata.java b/media/java/android/media/quality/AmbientBacklightMetadata.java
index 9c11f9a..5cea10d 100644
--- a/media/java/android/media/quality/AmbientBacklightMetadata.java
+++ b/media/java/android/media/quality/AmbientBacklightMetadata.java
@@ -29,6 +29,9 @@
/**
* Metadata of ambient backlight.
+ *
+ * <p>A metadata instance is sent from ambient backlight hardware in a {@link AmbientBacklightEvent}
+ * with {@link AmbientBacklightEvent#AMBIENT_BACKLIGHT_EVENT_METADATA}.
* @hide
*/
@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
@@ -44,10 +47,15 @@
private final int[] mZonesColors;
/**
- * Constructor of AmbientBacklightMetadata.
+ * Constructs AmbientBacklightMetadata.
*/
- public AmbientBacklightMetadata(@NonNull String packageName, int compressAlgorithm,
- int source, int colorFormat, int horizontalZonesNumber, int verticalZonesNumber,
+ public AmbientBacklightMetadata(
+ @NonNull String packageName,
+ @AmbientBacklightSettings.CompressAlgorithm int compressAlgorithm,
+ @AmbientBacklightSettings.Source int source,
+ @PixelFormat.Format int colorFormat,
+ int horizontalZonesNumber,
+ int verticalZonesNumber,
@NonNull int[] zonesColors) {
mPackageName = packageName;
mCompressAlgorithm = compressAlgorithm;
@@ -69,7 +77,7 @@
}
/**
- * Gets package name.
+ * Gets package name of the metadata.
* @hide
*/
@NonNull
@@ -102,7 +110,9 @@
}
/**
- * Gets the number of lights in each horizontal zone.
+ * Gets the number of horizontal color zones.
+ *
+ * <p>A color zone is a group of lights that always display the same color.
*/
@IntRange(from = 0)
public int getHorizontalZonesNumber() {
@@ -110,7 +120,9 @@
}
/**
- * Gets the number of lights in each vertical zone.
+ * Gets the number of vertical color zones.
+ *
+ * <p>A color zone is a group of lights that always display the same color.
*/
@IntRange(from = 0)
public int getVerticalZonesNumber() {
@@ -118,10 +130,11 @@
}
/**
+ * Gets color data of vertical color zones.
* @hide
*/
@NonNull
- public int[] getZonesColors() {
+ public int[] getVerticalZonesColors() {
return mZonesColors;
}
diff --git a/media/java/android/media/quality/AmbientBacklightSettings.java b/media/java/android/media/quality/AmbientBacklightSettings.java
index 4ed7bc7..d904cf7 100644
--- a/media/java/android/media/quality/AmbientBacklightSettings.java
+++ b/media/java/android/media/quality/AmbientBacklightSettings.java
@@ -30,7 +30,7 @@
import java.lang.annotation.RetentionPolicy;
/**
- * Settings for ambient backlight.
+ * Settings to configure ambient backlight hardware.
* @hide
*/
@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
@@ -124,8 +124,13 @@
/**
* Constructs AmbientBacklightSettings.
*/
- public AmbientBacklightSettings(int source, int maxFps, int colorFormat,
- int horizontalZonesNumber, int verticalZonesNumber, boolean isLetterboxOmitted,
+ public AmbientBacklightSettings(
+ @Source int source,
+ int maxFps,
+ @PixelFormat.Format int colorFormat,
+ int horizontalZonesNumber,
+ int verticalZonesNumber,
+ boolean isLetterboxOmitted,
int threshold) {
mSource = source;
mMaxFps = maxFps;
@@ -171,7 +176,9 @@
}
/**
- * Gets the number of lights in each horizontal zone.
+ * Gets the number of horizontal color zones.
+ *
+ * <p>A color zone is a group of lights that always display the same color.
*/
@IntRange(from = 0)
public int getHorizontalZonesNumber() {
@@ -179,7 +186,9 @@
}
/**
- * Gets the number of lights in each vertical zone.
+ * Gets the number of vertical color zones.
+ *
+ * <p>A color zone is a group of lights that always display the same color.
*/
@IntRange(from = 0)
public int getVerticalZonesNumber() {
@@ -187,7 +196,11 @@
}
/**
- * Returns {@code true} if letter box is omitted; {@code false} otherwise.
+ * Returns {@code true} if the black portion of the screen in letter box mode is omitted;
+ * {@code false} otherwise.
+ *
+ * <p>Letter-box is a technique to keep the original aspect ratio when displayed on a screen
+ * with different aspect ratio. Black bars are added to the top and bottom.
* @hide
*/
public boolean isLetterboxOmitted() {
@@ -195,6 +208,10 @@
}
/**
+ * Gets the detection threshold of the ambient light.
+ *
+ * <p>If the color of a color zone is changed by the difference is smaller than the threshold,
+ * the change is ignored.
* @hide
*/
public int getThreshold() {
diff --git a/media/java/android/media/quality/IMediaQualityManager.aidl b/media/java/android/media/quality/IMediaQualityManager.aidl
index 250d59b..aaedf21 100644
--- a/media/java/android/media/quality/IMediaQualityManager.aidl
+++ b/media/java/android/media/quality/IMediaQualityManager.aidl
@@ -42,10 +42,12 @@
SoundProfile createSoundProfile(in SoundProfile pp);
void updateSoundProfile(in String id, in SoundProfile pp);
void removeSoundProfile(in String id);
- SoundProfile getSoundProfileById(in String id);
+ SoundProfile getSoundProfile(in int type, in String name);
List<SoundProfile> getSoundProfilesByPackage(in String packageName);
List<SoundProfile> getAvailableSoundProfiles();
List<String> getSoundProfilePackageNames();
+ List<String> getSoundProfileAllowList();
+ void setSoundProfileAllowList(in List<String> packages);
void registerPictureProfileCallback(in IPictureProfileCallback cb);
void registerSoundProfileCallback(in ISoundProfileCallback cb);
diff --git a/media/java/android/media/quality/ISoundProfileCallback.aidl b/media/java/android/media/quality/ISoundProfileCallback.aidl
index 72d1524..9043757 100644
--- a/media/java/android/media/quality/ISoundProfileCallback.aidl
+++ b/media/java/android/media/quality/ISoundProfileCallback.aidl
@@ -17,6 +17,7 @@
package android.media.quality;
+import android.media.quality.ParamCapability;
import android.media.quality.SoundProfile;
/**
@@ -24,7 +25,9 @@
* @hide
*/
oneway interface ISoundProfileCallback {
- void onSoundProfileAdded(in long id, in SoundProfile p);
- void onSoundProfileUpdated(in long id, in SoundProfile p);
- void onSoundProfileRemoved(in long id, in SoundProfile p);
+ void onSoundProfileAdded(in String id, in SoundProfile p);
+ void onSoundProfileUpdated(in String id, in SoundProfile p);
+ void onSoundProfileRemoved(in String id, in SoundProfile p);
+ void onParamCapabilitiesChanged(in String id, in List<ParamCapability> caps);
+ void onError(in int err);
}
diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java
index 4d4526c..dcf4971 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -111,7 +111,7 @@
};
ISoundProfileCallback spCallback = new ISoundProfileCallback.Stub() {
@Override
- public void onSoundProfileAdded(long profileId, SoundProfile profile) {
+ public void onSoundProfileAdded(String profileId, SoundProfile profile) {
synchronized (mLock) {
for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
// TODO: filter callback record
@@ -120,7 +120,7 @@
}
}
@Override
- public void onSoundProfileUpdated(long profileId, SoundProfile profile) {
+ public void onSoundProfileUpdated(String profileId, SoundProfile profile) {
synchronized (mLock) {
for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
// TODO: filter callback record
@@ -129,7 +129,7 @@
}
}
@Override
- public void onSoundProfileRemoved(long profileId, SoundProfile profile) {
+ public void onSoundProfileRemoved(String profileId, SoundProfile profile) {
synchronized (mLock) {
for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
// TODO: filter callback record
@@ -137,6 +137,24 @@
}
}
}
+ @Override
+ public void onParamCapabilitiesChanged(String profileId, List<ParamCapability> caps) {
+ synchronized (mLock) {
+ for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
+ // TODO: filter callback record
+ record.postParamCapabilitiesChanged(profileId, caps);
+ }
+ }
+ }
+ @Override
+ public void onError(int err) {
+ synchronized (mLock) {
+ for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
+ // TODO: filter callback record
+ record.postError(err);
+ }
+ }
+ }
};
IAmbientBacklightCallback abCallback = new IAmbientBacklightCallback.Stub() {
@Override
@@ -331,14 +349,17 @@
/**
- * Gets sound profile by given profile ID.
- * @return the corresponding sound profile if available; {@code null} if the ID doesn't
- * exist or the profile is not accessible to the caller.
+ * Gets sound profile by given profile type and name.
+ *
+ * @return the corresponding sound profile if available; {@code null} if the name doesn't
+ * exist.
* @hide
*/
- public SoundProfile getSoundProfileById(String profileId) {
+ @Nullable
+ public SoundProfile getSoundProfile(
+ @SoundProfile.ProfileType int type, @NonNull String name) {
try {
- return mService.getSoundProfileById(profileId);
+ return mService.getSoundProfile(type, name);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -349,8 +370,9 @@
* @SystemApi gets profiles that available to the given package
* @hide
*/
+ @NonNull
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
- public List<SoundProfile> getSoundProfilesByPackage(String packageName) {
+ public List<SoundProfile> getSoundProfilesByPackage(@NonNull String packageName) {
try {
return mService.getSoundProfilesByPackage(packageName);
} catch (RemoteException e) {
@@ -362,6 +384,7 @@
* Gets profiles that available to the caller package
* @hide
*/
+ @NonNull
public List<SoundProfile> getAvailableSoundProfiles() {
try {
return mService.getAvailableSoundProfiles();
@@ -371,9 +394,12 @@
}
/**
- * @SystemApi all stored sound profiles of all packages
+ * @SystemApi Gets all package names whose sound profiles are available.
+ *
+ * @see #getSoundProfilesByPackage(String)
* @hide
*/
+ @NonNull
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
public List<String> getSoundProfilePackageNames() {
try {
@@ -387,12 +413,13 @@
/**
* Creates a sound profile and store it in the system.
*
- * @return the stored profile with an assigned profile ID.
+ * <p>If the profile is created successfully,
+ * {@link SoundProfileCallback#onSoundProfileAdded(long, SoundProfile)} is invoked.
* @hide
*/
- public SoundProfile createSoundProfile(SoundProfile sp) {
+ public void createSoundProfile(@NonNull SoundProfile sp) {
try {
- return mService.createSoundProfile(sp);
+ mService.createSoundProfile(sp);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -403,7 +430,7 @@
* Updates an existing sound profile and store it in the system.
* @hide
*/
- public void updateSoundProfile(String profileId, SoundProfile sp) {
+ public void updateSoundProfile(@NonNull String profileId, @NonNull SoundProfile sp) {
try {
mService.updateSoundProfile(profileId, sp);
} catch (RemoteException e) {
@@ -416,7 +443,7 @@
* Removes a sound profile from the system.
* @hide
*/
- public void removeSoundProfile(String profileId) {
+ public void removeSoundProfile(@NonNull String profileId) {
try {
mService.removeSoundProfile(profileId);
} catch (RemoteException e) {
@@ -468,6 +495,36 @@
}
/**
+ * Gets the allowlist of packages that can create and removed sound profiles
+ *
+ * @see #createSoundProfile(SoundProfile)
+ * @see #removeSoundProfile(String)
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
+ @NonNull
+ public List<String> getSoundProfileAllowList() {
+ try {
+ return mService.getSoundProfileAllowList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets the allowlist of packages that can create and removed sound profiles
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
+ public void setSoundProfileAllowList(@NonNull List<String> packageNames) {
+ try {
+ mService.setSoundProfileAllowList(packageNames);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns {@code true} if media quality HAL is implemented; {@code false} otherwise.
* @hide
*/
@@ -567,6 +624,7 @@
/**
* Registers a {@link AmbientBacklightCallback}.
+ * @hide
*/
public void registerAmbientBacklightCallback(
@NonNull @CallbackExecutor Executor executor,
@@ -580,6 +638,7 @@
/**
* Unregisters the existing {@link AmbientBacklightCallback}.
+ * @hide
*/
public void unregisterAmbientBacklightCallback(
@NonNull final AmbientBacklightCallback callback) {
@@ -600,6 +659,7 @@
* Set the ambient backlight settings.
*
* @param settings The settings to use for the backlight detector.
+ * @hide
*/
public void setAmbientBacklightSettings(
@NonNull AmbientBacklightSettings settings) {
@@ -615,6 +675,7 @@
* Enables or disables the ambient backlight detection.
*
* @param enabled {@code true} to enable, {@code false} to disable.
+ * @hide
*/
public void setAmbientBacklightEnabled(boolean enabled) {
try {
@@ -698,7 +759,7 @@
return mCallback;
}
- public void postSoundProfileAdded(final long id, SoundProfile profile) {
+ public void postSoundProfileAdded(final String id, SoundProfile profile) {
mExecutor.execute(new Runnable() {
@Override
@@ -708,7 +769,7 @@
});
}
- public void postSoundProfileUpdated(final long id, SoundProfile profile) {
+ public void postSoundProfileUpdated(final String id, SoundProfile profile) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
@@ -717,7 +778,7 @@
});
}
- public void postSoundProfileRemoved(final long id, SoundProfile profile) {
+ public void postSoundProfileRemoved(final String id, SoundProfile profile) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
@@ -725,6 +786,24 @@
}
});
}
+
+ public void postParamCapabilitiesChanged(final String id, List<ParamCapability> caps) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onParamCapabilitiesChanged(id, caps);
+ }
+ });
+ }
+
+ public void postError(int error) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onError(error);
+ }
+ });
+ }
}
private static final class AmbientBacklightCallbackRecord {
@@ -801,12 +880,13 @@
* This is invoked when parameter capabilities has been changed due to status changes of the
* content.
*
- * @param profileId the ID of the profile used by the media content.
+ * @param profileId the ID of the profile used by the media content. {@code null} if there
+ * is no associated profile
* @param updatedCaps the updated capabilities.
* @hide
*/
public void onParamCapabilitiesChanged(
- @NonNull String profileId, @NonNull List<ParamCapability> updatedCaps) {
+ @Nullable String profileId, @NonNull List<ParamCapability> updatedCaps) {
}
}
@@ -816,29 +896,64 @@
*/
public abstract static class SoundProfileCallback {
/**
+ * This is invoked when a sound profile has been added.
+ *
+ * @param profileId the ID of the profile.
+ * @param profile the newly added profile.
* @hide
*/
- public void onSoundProfileAdded(long id, SoundProfile profile) {
+ public void onSoundProfileAdded(
+ @NonNull String profileId, @NonNull SoundProfile profile) {
}
+
/**
+ * This is invoked when a sound profile has been updated.
+ *
+ * @param profileId the ID of the profile.
+ * @param profile the profile with updated info.
* @hide
*/
- public void onSoundProfileUpdated(long id, SoundProfile profile) {
+ public void onSoundProfileUpdated(
+ @NonNull String profileId, @NonNull SoundProfile profile) {
}
+
/**
+ * This is invoked when a sound profile has been removed.
+ *
+ * @param profileId the ID of the profile.
+ * @param profile the removed profile.
* @hide
*/
- public void onSoundProfileRemoved(long id, SoundProfile profile) {
+ public void onSoundProfileRemoved(
+ @NonNull String profileId, @NonNull SoundProfile profile) {
}
+
/**
+ * This is invoked when an issue has occurred.
+ *
+ * @param errorCode the error code
* @hide
*/
- public void onError(int errorCode) {
+ public void onError(@SoundProfile.ErrorCode int errorCode) {
+ }
+
+ /**
+ * This is invoked when parameter capabilities has been changed due to status changes of the
+ * content.
+ *
+ * @param profileId the ID of the profile used by the media content. {@code null} if there
+ * is no associated profile
+ * @param updatedCaps the updated capabilities.
+ * @hide
+ */
+ public void onParamCapabilitiesChanged(
+ @Nullable String profileId, @NonNull List<ParamCapability> updatedCaps) {
}
}
/**
* Callback used to monitor status of ambient backlight.
+ * @hide
*/
public abstract static class AmbientBacklightCallback {
/**
diff --git a/media/java/android/media/quality/SoundProfile.java b/media/java/android/media/quality/SoundProfile.java
index 20d117b..de93afe 100644
--- a/media/java/android/media/quality/SoundProfile.java
+++ b/media/java/android/media/quality/SoundProfile.java
@@ -17,54 +17,119 @@
package android.media.quality;
import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.media.tv.TvInputInfo;
import android.media.tv.flags.Flags;
-import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.PersistableBundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresPermission;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
+ * Profile for sound quality.
* @hide
*/
@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
public class SoundProfile implements Parcelable {
@Nullable
- private Long mId;
+ private String mId;
+ private final int mType;
@NonNull
private final String mName;
@Nullable
private final String mInputId;
- @Nullable
+ @NonNull
private final String mPackageName;
@NonNull
- private final Bundle mParams;
+ private final PersistableBundle mParams;
- protected SoundProfile(Parcel in) {
- if (in.readByte() == 0) {
- mId = null;
- } else {
- mId = in.readLong();
- }
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = false, prefix = "TYPE_", value = {
+ TYPE_SYSTEM,
+ TYPE_APPLICATION})
+ public @interface ProfileType {}
+
+ /**
+ * System profile type.
+ *
+ * <p>A profile of system type is managed by the system, and readable to the package returned by
+ * {@link #getPackageName()}.
+ */
+ public static final int TYPE_SYSTEM = 1;
+ /**
+ * Application profile type.
+ *
+ * <p>A profile of application type is managed by the package returned by
+ * {@link #getPackageName()}.
+ */
+ public static final int TYPE_APPLICATION = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = false, prefix = "ERROR_", value = {
+ ERROR_UNKNOWN,
+ ERROR_NO_PERMISSION,
+ ERROR_DUPLICATE,
+ ERROR_INVALID_ARGUMENT,
+ ERROR_NOT_ALLOWLISTED
+ })
+ public @interface ErrorCode {}
+
+ /**
+ * Error code for unknown errors.
+ */
+ public static final int ERROR_UNKNOWN = 0;
+
+ /**
+ * Error code for missing necessary permission to handle the profiles.
+ */
+ public static final int ERROR_NO_PERMISSION = 1;
+
+ /**
+ * Error code for creating a profile with existing profile type and name.
+ *
+ * @see #getProfileType()
+ * @see #getName()
+ */
+ public static final int ERROR_DUPLICATE = 2;
+
+ /**
+ * Error code for invalid argument.
+ */
+ public static final int ERROR_INVALID_ARGUMENT = 3;
+
+ /**
+ * Error code for the case when an operation requires an allowlist but the caller is not in the
+ * list.
+ *
+ * @see MediaQualityManager#getSoundProfileAllowList()
+ */
+ public static final int ERROR_NOT_ALLOWLISTED = 4;
+
+ protected SoundProfile(@NonNull Parcel in) {
+ mId = in.readString();
+ mType = in.readInt();
mName = in.readString();
mInputId = in.readString();
mPackageName = in.readString();
- mParams = in.readBundle();
+ mParams = in.readPersistableBundle();
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
- if (mId == null) {
- dest.writeByte((byte) 0);
- } else {
- dest.writeByte((byte) 1);
- dest.writeLong(mId);
- }
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mId);
+ dest.writeInt(mType);
dest.writeString(mName);
dest.writeString(mInputId);
dest.writeString(mPackageName);
- dest.writeBundle(mParams);
+ dest.writePersistableBundle(mParams);
}
@Override
@@ -72,6 +137,7 @@
return 0;
}
+ @NonNull
public static final Creator<SoundProfile> CREATOR = new Creator<SoundProfile>() {
@Override
public SoundProfile createFromParcel(Parcel in) {
@@ -91,93 +157,164 @@
* @hide
*/
public SoundProfile(
- @Nullable Long id,
+ @Nullable String id,
+ int type,
@NonNull String name,
@Nullable String inputId,
- @Nullable String packageName,
- @NonNull Bundle params) {
+ @NonNull String packageName,
+ @NonNull PersistableBundle params) {
this.mId = id;
+ this.mType = type;
this.mName = name;
- com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, name);
this.mInputId = inputId;
this.mPackageName = packageName;
this.mParams = params;
}
+ /**
+ * Gets profile ID.
+ *
+ * <p>A profile ID is a globally unique ID generated and assigned by the system. For profile
+ * objects retrieved from system (e.g {@link MediaQualityManager#getAvailableSoundProfiles()})
+ * this profile ID is non-null; For profiles built locally with {@link Builder}, it's
+ * {@code null}.
+ *
+ * @return the unique profile ID; {@code null} if the profile is built locally with
+ * {@link Builder}.
+ */
@Nullable
- public Long getProfileId() {
+ public String getProfileId() {
return mId;
}
+ /**
+ * Only used by system to assign the ID.
+ * @hide
+ */
+ public void setProfileId(String id) {
+ mId = id;
+ }
+
+ /**
+ * Gets profile type.
+ */
+ @ProfileType
+ public int getProfileType() {
+ return mType;
+ }
+
+ /**
+ * Gets the profile name.
+ */
@NonNull
public String getName() {
return mName;
}
+ /**
+ * Gets the input ID if the profile is for a TV input.
+ *
+ * @return the corresponding TV input ID; {@code null} if the profile is not associated with a
+ * TV input.
+ *
+ * @see TvInputInfo#getId()
+ */
@Nullable
public String getInputId() {
return mInputId;
}
+ /**
+ * Gets the package name of this profile.
+ *
+ * <p>The package name defines the user of a profile. Only this specific package and system app
+ * can access to this profile.
+ *
+ * @return the package name; {@code null} if the profile is built locally using
+ * {@link Builder} and the package is not set.
+ */
@Nullable
public String getPackageName() {
return mPackageName;
}
+
+ /**
+ * Gets the parameters of this profile.
+ *
+ * <p>The keys of commonly used parameters can be found in
+ * {@link MediaQualityContract.SoundQuality}.
+ */
@NonNull
- public Bundle getParameters() {
- return new Bundle(mParams);
+ public PersistableBundle getParameters() {
+ return new PersistableBundle(mParams);
}
/**
* A builder for {@link SoundProfile}
+ * @hide
*/
public static class Builder {
@Nullable
- private Long mId;
+ private String mId;
+ private int mType = TYPE_APPLICATION;
@NonNull
private String mName;
@Nullable
private String mInputId;
- @Nullable
+ @NonNull
private String mPackageName;
@NonNull
- private Bundle mParams;
+ private PersistableBundle mParams;
/**
* Creates a new Builder.
- *
- * @hide
*/
public Builder(@NonNull String name) {
mName = name;
- com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, name);
}
/**
- * Copy constructor.
- *
- * @hide
+ * Copy constructor of builder.
*/
public Builder(@NonNull SoundProfile p) {
mId = null; // ID needs to be reset
+ mType = p.getProfileType();
mName = p.getName();
mPackageName = p.getPackageName();
mInputId = p.getInputId();
+ mParams = p.getParameters();
}
/**
- * Sets profile ID.
- * @hide using by MediaQualityService
+ * Only used by system to assign the ID.
+ * @hide
*/
@NonNull
- public Builder setProfileId(@Nullable Long id) {
+ public Builder setProfileId(@Nullable String id) {
mId = id;
return this;
}
/**
- * Sets input ID.
+ * Sets profile type.
+ *
+ * @hide
*/
+ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
+ @NonNull
+ public Builder setProfileType(@ProfileType int value) {
+ mType = value;
+ return this;
+ }
+
+ /**
+ * Sets input ID.
+ *
+ * @see SoundProfile#getInputId()
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
@NonNull
public Builder setInputId(@NonNull String value) {
mInputId = value;
@@ -186,7 +323,12 @@
/**
* Sets package name of the profile.
+ *
+ * @see SoundProfile#getPackageName()
+ *
+ * @hide
*/
+ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
@NonNull
public Builder setPackageName(@NonNull String value) {
mPackageName = value;
@@ -195,12 +337,15 @@
/**
* Sets profile parameters.
+ *
+ * @see SoundProfile#getParameters()
*/
@NonNull
- public Builder setParameters(@NonNull Bundle params) {
- mParams = new Bundle(params);
+ public Builder setParameters(@NonNull PersistableBundle params) {
+ mParams = new PersistableBundle(params);
return this;
}
+
/**
* Builds the instance.
*/
@@ -209,6 +354,7 @@
SoundProfile o = new SoundProfile(
mId,
+ mType,
mName,
mInputId,
mPackageName,
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 23dd9b7..4180710 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -45,8 +45,10 @@
header_libs: [
"jni_headers",
+ "native_headers",
"libhwui_internal_headers",
],
+ export_header_lib_headers: ["native_headers"],
static_libs: [
"libarect",
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 79a0607..f587660 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -57,6 +57,7 @@
@FlaggedApi("android.nfc.nfc_oem_extension") public final class NfcOemExtension {
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void clearPreference();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int forceRoutingTableCommit();
method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull public java.util.List<java.lang.String> getActiveNfceeList();
method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.nfc.RoutingStatus getRoutingStatus();
method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.List<android.nfc.NfcRoutingTableEntry> getRoutingTable();
@@ -73,6 +74,9 @@
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void synchronizeScreenState();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void triggerInitialization();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void unregisterCallback(@NonNull android.nfc.NfcOemExtension.Callback);
+ field public static final int COMMIT_ROUTING_STATUS_FAILED = 3; // 0x3
+ field public static final int COMMIT_ROUTING_STATUS_FAILED_UPDATE_IN_PROGRESS = 6; // 0x6
+ field public static final int COMMIT_ROUTING_STATUS_OK = 0; // 0x0
field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int DISABLE = 0; // 0x0
field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int ENABLE_DEFAULT = 1; // 0x1
field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int ENABLE_EE = 3; // 0x3
@@ -96,6 +100,7 @@
method public void onEnableFinished(int);
method public void onEnableRequested(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onEnableStarted();
+ method public void onExtractOemPackages(@NonNull android.nfc.NdefMessage, @NonNull java.util.function.Consumer<java.util.List<java.lang.String>>);
method public void onGetOemAppSearchIntent(@NonNull java.util.List<java.lang.String>, @NonNull java.util.function.Consumer<android.content.Intent>);
method public void onHceEventReceived(int);
method public void onLaunchHceAppChooserActivity(@NonNull String, @NonNull java.util.List<android.nfc.cardemulation.ApduServiceInfo>, @NonNull android.content.ComponentName, @NonNull String);
@@ -106,7 +111,7 @@
method public void onReaderOptionChanged(boolean);
method public void onRfDiscoveryStarted(boolean);
method public void onRfFieldActivated(boolean);
- method public void onRoutingChanged();
+ method public void onRoutingChanged(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onRoutingTableFull();
method public void onStateUpdated(int);
method public void onTagConnected(boolean);
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index 40fd068..31514a0 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -120,4 +120,5 @@
boolean isTagPresent();
List<Entry> getRoutingTableEntryList();
void indicateDataMigration(boolean inProgress, String pkg);
+ int commitRouting();
}
diff --git a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
index fb793b0..1a21c0b 100644
--- a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
+++ b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
@@ -41,7 +41,7 @@
void onEnableFinished(int status);
void onDisableFinished(int status);
void onTagDispatch(in ResultReceiver isSkipped);
- void onRoutingChanged();
+ void onRoutingChanged(in ResultReceiver isSkipped);
void onHceEventReceived(int action);
void onReaderOptionChanged(boolean enabled);
void onCardEmulationActivated(boolean isActivated);
@@ -54,4 +54,5 @@
void onLaunchHceTapAgainActivity(in ApduServiceInfo service, in String category);
void onRoutingTableFull();
void onLogEventNotified(in OemLogItems item);
+ void onExtractOemPackages(in NdefMessage message, in ResultReceiver packageReceiver);
}
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index c677cd6..326ca64 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -23,6 +23,7 @@
import android.Manifest;
import android.annotation.CallbackExecutor;
+import android.annotation.DurationMillisLong;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -193,6 +194,30 @@
public @interface StatusCode {}
/**
+ * Routing commit succeeded.
+ */
+ public static final int COMMIT_ROUTING_STATUS_OK = 0;
+ /**
+ * Routing commit failed.
+ */
+ public static final int COMMIT_ROUTING_STATUS_FAILED = 3;
+ /**
+ * Routing commit failed due to the update is in progress.
+ */
+ public static final int COMMIT_ROUTING_STATUS_FAILED_UPDATE_IN_PROGRESS = 6;
+
+ /**
+ * Status codes returned when calling {@link #forceRoutingTableCommit()}
+ * @hide
+ */
+ @IntDef(prefix = "COMMIT_ROUTING_STATUS_", value = {
+ COMMIT_ROUTING_STATUS_OK,
+ COMMIT_ROUTING_STATUS_FAILED,
+ COMMIT_ROUTING_STATUS_FAILED_UPDATE_IN_PROGRESS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CommitRoutingStatusCode {}
+ /**
* Interface for Oem extensions for NFC.
*/
public interface Callback {
@@ -285,8 +310,12 @@
/**
* Notifies routing configuration is changed.
+ * @param isCommitRoutingSkipped The {@link Consumer} to be
+ * completed. If routing commit should be skipped,
+ * the {@link Consumer#accept(Object)} should be called with
+ * {@link Boolean#TRUE}, otherwise call with {@link Boolean#FALSE}.
*/
- void onRoutingChanged();
+ void onRoutingChanged(@NonNull Consumer<Boolean> isCommitRoutingSkipped);
/**
* API to activate start stop cpu boost on hce event.
@@ -403,6 +432,19 @@
* @param item the log items that contains log information of NFC event.
*/
void onLogEventNotified(@NonNull OemLogItems item);
+
+ /**
+ * Callback to to extract OEM defined packages from given NDEF message when
+ * a NFC tag is detected. These are used to handle NFC tags encoded with a
+ * proprietary format for storing app name (Android native app format).
+ *
+ * @param message NDEF message containing OEM package names
+ * @param packageConsumer The {@link Consumer} to be completed.
+ * The {@link Consumer#accept(Object)} should be called with
+ * the list of package names.
+ */
+ void onExtractOemPackages(@NonNull NdefMessage message,
+ @NonNull Consumer<List<String>> packageConsumer);
}
@@ -603,12 +645,12 @@
/**
* Pauses NFC tag reader mode polling for a {@code timeoutInMs} millisecond.
* In case of {@code timeoutInMs} is zero or invalid polling will be stopped indefinitely
- * use {@link #resumePolling() to resume the polling.
- * @param timeoutInMs the pause polling duration in millisecond
+ * use {@link #resumePolling()} to resume the polling.
+ * @param timeoutInMs the pause polling duration in millisecond, ranging from 0 to 40000.
*/
@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
- public void pausePolling(int timeoutInMs) {
+ public void pausePolling(@DurationMillisLong int timeoutInMs) {
NfcAdapter.callService(() -> NfcAdapter.sService.pausePolling(timeoutInMs));
}
@@ -739,6 +781,18 @@
return result;
}
+ /**
+ * API to force a routing table commit.
+ * @return a {@link StatusCode} to indicate if commit routing succeeded or not
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @CommitRoutingStatusCode
+ public int forceRoutingTableCommit() {
+ return NfcAdapter.callServiceReturn(
+ () -> NfcAdapter.sService.commitRouting(), COMMIT_ROUTING_STATUS_FAILED);
+ }
+
private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub {
@Override
@@ -842,9 +896,10 @@
new ReceiverWrapper<>(isSkipped), cb::onTagDispatch, ex));
}
@Override
- public void onRoutingChanged() throws RemoteException {
+ public void onRoutingChanged(ResultReceiver isSkipped) throws RemoteException {
mCallbackMap.forEach((cb, ex) ->
- handleVoidCallback(null, (Object input) -> cb.onRoutingChanged(), ex));
+ handleVoidCallback(
+ new ReceiverWrapper<>(isSkipped), cb::onRoutingChanged, ex));
}
@Override
public void onHceEventReceived(int action) throws RemoteException {
@@ -923,6 +978,15 @@
handleVoidCallback(item, cb::onLogEventNotified, ex));
}
+ @Override
+ public void onExtractOemPackages(NdefMessage message, ResultReceiver packageConsumer)
+ throws RemoteException {
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoid2ArgCallback(message,
+ new ReceiverWrapper<>(packageConsumer),
+ cb::onExtractOemPackages, ex));
+ }
+
private <T> void handleVoidCallback(
T input, Consumer<T> callbackMethod, Executor executor) {
synchronized (mLock) {
@@ -1033,8 +1097,14 @@
Bundle bundle = new Bundle();
bundle.putParcelable("intent", (Intent) result);
mResultReceiver.send(0, bundle);
+ } else if (result instanceof List<?> list) {
+ if (list.stream().allMatch(String.class::isInstance)) {
+ Bundle bundle = new Bundle();
+ bundle.putStringArray("packageNames",
+ list.stream().map(pkg -> (String) pkg).toArray(String[]::new));
+ mResultReceiver.send(0, bundle);
+ }
}
-
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
index 5eeb49a..6842d0a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
@@ -204,6 +204,13 @@
return this;
}
+ public TestModeBuilder implicitForPackage(String pkg) {
+ setPackage(pkg);
+ setId(ZenModeConfig.implicitRuleId(pkg));
+ setName("Do Not Disturb (" + pkg + ")");
+ return this;
+ }
+
public TestModeBuilder setActive(boolean active) {
if (active) {
mConfigZenRule.enabled = true;
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
index 34d3bd9..d5cfe55 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -208,6 +208,11 @@
}
@NonNull
+ public Kind getKind() {
+ return mKind;
+ }
+
+ @NonNull
public Status getStatus() {
return mStatus;
}
diff --git a/packages/SettingsProvider/res/xml/bookmarks.xml b/packages/SettingsProvider/res/xml/bookmarks.xml
index 22d0226..645b275 100644
--- a/packages/SettingsProvider/res/xml/bookmarks.xml
+++ b/packages/SettingsProvider/res/xml/bookmarks.xml
@@ -32,6 +32,9 @@
'y': YouTube
-->
<bookmarks>
+ <!-- TODO(b/358569822): Remove this from Settings DB
+ This is legacy implementation to store bookmarks in Settings DB, which is deprecated and
+ no longer used -->
<bookmark
role="android.app.role.BROWSER"
shortcut="b" />
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index a4b8821..123f823 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1748,6 +1748,13 @@
}
flag {
+ name: "notification_shade_blur"
+ namespace: "systemui"
+ description: "Enables the new blur effect on the Notification Shade."
+ bug: "370555223"
+}
+
+flag {
name: "ensure_enr_views_visibility"
namespace: "systemui"
description: "Ensures public and private visibilities"
@@ -1790,4 +1797,4 @@
metadata {
purpose: PURPOSE_BUGFIX
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 96e99b1..778d7e7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -67,6 +67,7 @@
dialogFactory = dialogFactory,
widgetSection = widgetSection,
modifier = Modifier.element(Communal.Elements.Grid),
+ sceneScope = this@Content,
)
}
with(lockSection) {
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 df1185c..5b1203f 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
@@ -163,6 +163,7 @@
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.window.layout.WindowMetricsCalculator
import com.android.compose.animation.Easings.Emphasized
+import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.thenIf
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
import com.android.internal.R.dimen.system_app_widget_background_radius
@@ -186,7 +187,9 @@
import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import kotlin.math.max
import kotlin.math.min
@@ -205,6 +208,7 @@
widgetConfigurator: WidgetConfigurator? = null,
onOpenWidgetPicker: (() -> Unit)? = null,
onEditDone: (() -> Unit)? = null,
+ sceneScope: SceneScope? = null,
) {
val communalContent by
viewModel.communalContent.collectAsStateWithLifecycle(initialValue = emptyList())
@@ -414,6 +418,7 @@
widgetConfigurator = widgetConfigurator,
interactionHandler = interactionHandler,
widgetSection = widgetSection,
+ sceneScope = sceneScope,
)
}
}
@@ -735,6 +740,7 @@
widgetConfigurator: WidgetConfigurator?,
interactionHandler: RemoteViews.InteractionHandler?,
widgetSection: CommunalAppWidgetSection,
+ sceneScope: SceneScope?,
) {
var gridModifier =
Modifier.align(Alignment.TopStart).onGloballyPositioned { setGridCoordinates(it) }
@@ -871,6 +877,7 @@
interactionHandler = interactionHandler,
widgetSection = widgetSection,
resizeableItemFrameViewModel = resizeableItemFrameViewModel,
+ sceneScope = sceneScope,
)
}
}
@@ -1095,6 +1102,7 @@
interactionHandler: RemoteViews.InteractionHandler?,
widgetSection: CommunalAppWidgetSection,
resizeableItemFrameViewModel: ResizeableItemFrameViewModel,
+ sceneScope: SceneScope? = null,
) {
when (model) {
is CommunalContentModel.WidgetContent.Widget ->
@@ -1118,7 +1126,7 @@
is CommunalContentModel.CtaTileInViewMode -> CtaTileInViewModeContent(viewModel, modifier)
is CommunalContentModel.Smartspace -> SmartspaceContent(interactionHandler, model, modifier)
is CommunalContentModel.Tutorial -> TutorialContent(modifier)
- is CommunalContentModel.Umo -> Umo(viewModel, modifier)
+ is CommunalContentModel.Umo -> Umo(viewModel, sceneScope, modifier)
}
}
@@ -1529,7 +1537,25 @@
}
@Composable
-private fun Umo(viewModel: BaseCommunalViewModel, modifier: Modifier = Modifier) {
+private fun Umo(
+ viewModel: BaseCommunalViewModel,
+ sceneScope: SceneScope?,
+ modifier: Modifier = Modifier,
+) {
+ if (SceneContainerFlag.isEnabled && sceneScope != null) {
+ sceneScope.MediaCarousel(
+ modifier = modifier.fillMaxSize(),
+ isVisible = true,
+ mediaHost = viewModel.mediaHost,
+ carouselController = viewModel.mediaCarouselController,
+ )
+ } else {
+ UmoLegacy(viewModel, modifier)
+ }
+}
+
+@Composable
+private fun UmoLegacy(viewModel: BaseCommunalViewModel, modifier: Modifier = Modifier) {
AndroidView(
modifier =
modifier.pointerInput(Unit) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
index 163f4b3..78e6056 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
@@ -307,6 +307,6 @@
private object DraggableBottomSheet {
val DefaultTopPadding = 64.dp
- val LargeScreenTopPadding = 72.dp
+ val LargeScreenTopPadding = 56.dp
val MaxWidth = 640.dp
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 44f60cb..eb2a016 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -350,8 +350,7 @@
val placeInThisContent =
elementContentWhenIdle(
layoutImpl,
- currentState.currentScene,
- currentState.currentOverlays,
+ currentState,
isInContent = { it in element.stateByContent },
) == content.key
@@ -639,20 +638,11 @@
internal inline fun elementContentWhenIdle(
layoutImpl: SceneTransitionLayoutImpl,
- idle: TransitionState.Idle,
+ currentState: TransitionState,
isInContent: (ContentKey) -> Boolean,
): ContentKey {
- val currentScene = idle.currentScene
- val overlays = idle.currentOverlays
- return elementContentWhenIdle(layoutImpl, currentScene, overlays, isInContent)
-}
-
-private inline fun elementContentWhenIdle(
- layoutImpl: SceneTransitionLayoutImpl,
- currentScene: SceneKey,
- overlays: Set<OverlayKey>,
- isInContent: (ContentKey) -> Boolean,
-): ContentKey {
+ val currentScene = currentState.currentScene
+ val overlays = currentState.currentOverlays
if (overlays.isEmpty()) {
return currentScene
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index c790ff0..509a16c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -188,7 +188,9 @@
return when (
val elementState = movableElementState(element, layoutImpl.state.transitionStates)
) {
- null -> false
+ null ->
+ movableElementContentWhenIdle(layoutImpl, element, layoutImpl.state.transitionState) ==
+ content
is TransitionState.Idle ->
movableElementContentWhenIdle(layoutImpl, element, elementState) == content
is TransitionState.Transition -> {
@@ -217,7 +219,7 @@
private fun movableElementContentWhenIdle(
layoutImpl: SceneTransitionLayoutImpl,
element: MovableElementKey,
- elementState: TransitionState.Idle,
+ elementState: TransitionState,
): ContentKey {
val contents = element.contentPicker.contents
return elementContentWhenIdle(layoutImpl, elementState, isInContent = { contents.contains(it) })
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 09156d5..3bf2ed5 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -356,6 +356,12 @@
override suspend fun startTransition(transition: TransitionState.Transition, chain: Boolean) {
checkThread()
+ // Prepare the transition before starting it. This is outside of the try/finally block on
+ // purpose because preparing a transition might throw an exception (e.g. if we find multiple
+ // specs matching this transition), in which case we want to throw that exception here
+ // before even starting the transition.
+ prepareTransitionBeforeStarting(transition)
+
try {
// Start the transition.
startTransitionInternal(transition, chain)
@@ -367,7 +373,7 @@
}
}
- private fun startTransitionInternal(transition: TransitionState.Transition, chain: Boolean) {
+ private fun prepareTransitionBeforeStarting(transition: TransitionState.Transition) {
// Set the current scene and overlays on the transition.
val currentState = transitionState
transition.currentSceneWhenTransitionStarted = currentState.currentScene
@@ -395,7 +401,9 @@
} else {
transition.updateOverscrollSpecs(fromSpec = null, toSpec = null)
}
+ }
+ private fun startTransitionInternal(transition: TransitionState.Transition, chain: Boolean) {
when (val currentState = transitionStates.last()) {
is TransitionState.Idle -> {
// Replace [Idle] by [transition].
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
index 09b5939..b4c8ad7 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
@@ -35,6 +35,7 @@
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertPositionInRootIsEqualTo
import androidx.compose.ui.test.hasParent
+import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onAllNodesWithText
@@ -404,4 +405,40 @@
rule.waitForIdle()
rule.onNodeWithTag(fooParentInOverlayTag).assertSizeIsEqualTo(fooSize)
}
+
+ @Test
+ fun movableElementInOverlayShouldBeComposed() {
+ val fooKey = MovableElementKey("foo", contents = setOf(OverlayA))
+ val fooContentTag = "fooContentTag"
+
+ @Composable
+ fun ContentScope.MovableFoo(modifier: Modifier = Modifier) {
+ MovableElement(fooKey, modifier) {
+ content { Box(Modifier.testTag(fooContentTag).size(100.dp)) }
+ }
+ }
+
+ val state =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ initialScene = SceneA,
+ initialOverlays = setOf(OverlayA),
+ )
+ }
+
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayout(state) {
+ scene(SceneA) { Box(Modifier.fillMaxSize()) }
+ overlay(OverlayA) { MovableFoo() }
+ overlay(OverlayB) { Box(Modifier.size(50.dp)) }
+ }
+ }
+
+ rule.onNode(hasTestTag(fooContentTag)).assertIsDisplayed().assertSizeIsEqualTo(100.dp)
+
+ // Show overlay B. This shouldn't have any impact on Foo that should still be composed in A.
+ scope.launch { state.startTransition(transition(SceneA, OverlayB)) }
+ rule.onNode(hasTestTag(fooContentTag)).assertIsDisplayed().assertSizeIsEqualTo(100.dp)
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 397ca0e..79ca891 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -686,4 +686,30 @@
assertThat(state.transitionState).isIdle()
assertThat(job.isCancelled).isTrue()
}
+
+ @Test
+ fun badTransitionSpecThrowsMeaningfulMessageWhenStartingTransition() {
+ val state =
+ MutableSceneTransitionLayoutState(
+ SceneA,
+ transitions {
+ // This transition definition is bad because they both match when transitioning
+ // from A to B.
+ from(SceneA) {}
+ to(SceneB) {}
+ },
+ )
+
+ val exception =
+ assertThrows(IllegalStateException::class.java) {
+ runBlocking { state.startTransition(transition(from = SceneA, to = SceneB)) }
+ }
+
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo(
+ "Found multiple transition specs for transition SceneKey(debugName=SceneA) => " +
+ "SceneKey(debugName=SceneB)"
+ )
+ }
}
diff --git a/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml
index 651e401..18073ad 100644
--- a/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml
@@ -16,4 +16,5 @@
<resources>
<dimen name="keyguard_smartspace_top_offset">0dp</dimen>
+ <dimen name="status_view_margin_horizontal">8dp</dimen>
</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values-sw600dp/dimens.xml b/packages/SystemUI/customization/res/values-sw600dp/dimens.xml
index 10e630d..37cd590 100644
--- a/packages/SystemUI/customization/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/customization/res/values-sw600dp/dimens.xml
@@ -17,4 +17,5 @@
<resources>
<!-- For portrait direction in unfold foldable device, we don't need keyguard_smartspace_top_offset-->
<dimen name="keyguard_smartspace_top_offset">0dp</dimen>
+ <dimen name="status_view_margin_horizontal">62dp</dimen>
</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/customization/res/values-sw720dp-land/dimens.xml
new file mode 100644
index 0000000..c1cf42c
--- /dev/null
+++ b/packages/SystemUI/customization/res/values-sw720dp-land/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+
+<resources>
+ <dimen name="status_view_margin_horizontal">24dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/customization/res/values-sw720dp-port/dimens.xml
new file mode 100644
index 0000000..54dbeaa
--- /dev/null
+++ b/packages/SystemUI/customization/res/values-sw720dp-port/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+
+<resources>
+ <dimen name="status_view_margin_horizontal">124dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values/dimens.xml b/packages/SystemUI/customization/res/values/dimens.xml
index 7feea6e..041ae62 100644
--- a/packages/SystemUI/customization/res/values/dimens.xml
+++ b/packages/SystemUI/customization/res/values/dimens.xml
@@ -39,4 +39,5 @@
<!--Dimens used in both lockscreen preview and smartspace -->
<dimen name="date_weather_view_height">24dp</dimen>
<dimen name="enhanced_smartspace_height">104dp</dimen>
+ <dimen name="status_view_margin_horizontal">0dp</dimen>
</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 935737c..5317ac1 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -154,7 +154,7 @@
alignment =
DigitalAlignment(
HorizontalAlignment.CENTER,
- VerticalAlignment.CENTER,
+ VerticalAlignment.BASELINE,
),
dateTimeFormat = "hh",
),
@@ -173,7 +173,7 @@
alignment =
DigitalAlignment(
HorizontalAlignment.CENTER,
- VerticalAlignment.CENTER,
+ VerticalAlignment.BASELINE,
),
dateTimeFormat = "hh",
),
@@ -192,7 +192,7 @@
alignment =
DigitalAlignment(
HorizontalAlignment.CENTER,
- VerticalAlignment.CENTER,
+ VerticalAlignment.BASELINE,
),
dateTimeFormat = "mm",
),
@@ -211,7 +211,7 @@
alignment =
DigitalAlignment(
HorizontalAlignment.CENTER,
- VerticalAlignment.CENTER,
+ VerticalAlignment.BASELINE,
),
dateTimeFormat = "mm",
),
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
index 27ed099..faef18c 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
@@ -18,6 +18,7 @@
import android.graphics.Canvas
import android.graphics.Point
+import android.icu.text.NumberFormat
import android.util.MathUtils.constrainedMap
import android.view.View
import android.view.ViewGroup
@@ -26,6 +27,7 @@
import com.android.systemui.customization.R
import com.android.systemui.shared.clocks.ClockContext
import com.android.systemui.shared.clocks.DigitTranslateAnimator
+import java.util.Locale
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
@@ -34,10 +36,14 @@
class FlexClockView(clockCtx: ClockContext) : DigitalClockFaceView(clockCtx) {
override var digitalClockTextViewMap = mutableMapOf<Int, SimpleDigitalClockTextView>()
- val digitLeftTopMap = mutableMapOf<Int, Point>()
- var maxSingleDigitSize = Point(-1, -1)
- val lockscreenTranslate = Point(0, 0)
- var aodTranslate = Point(0, 0)
+ private val digitLeftTopMap = mutableMapOf<Int, Point>()
+
+ private var maxSingleDigitSize = Point(-1, -1)
+ private val lockscreenTranslate = Point(0, 0)
+ private var aodTranslate = Point(0, 0)
+
+ // Does the current language have mono vertical size when displaying numerals
+ private var isMonoVerticalNumericLineSpacing = true
init {
setWillNotDraw(false)
@@ -46,6 +52,7 @@
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
)
+ updateLocale(Locale.getDefault())
}
private val digitOffsets = mutableMapOf<Int, Float>()
@@ -58,12 +65,19 @@
protected override fun calculateSize(widthMeasureSpec: Int, heightMeasureSpec: Int): Point {
maxSingleDigitSize = Point(-1, -1)
+ val bottomLocation: (textView: SimpleDigitalClockTextView) -> Int = { textView ->
+ if (isMonoVerticalNumericLineSpacing) {
+ maxSingleDigitSize.y
+ } else {
+ (textView.paint.fontMetrics.descent - textView.paint.fontMetrics.ascent).toInt()
+ }
+ }
+
digitalClockTextViewMap.forEach { (_, textView) ->
textView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
maxSingleDigitSize.x = max(maxSingleDigitSize.x, textView.measuredWidth)
- maxSingleDigitSize.y = max(maxSingleDigitSize.y, textView.measuredHeight)
+ maxSingleDigitSize.y = max(bottomLocation(textView), textView.measuredHeight)
}
- val textView = digitalClockTextViewMap[R.id.HOUR_FIRST_DIGIT]!!
aodTranslate = Point(0, 0)
return Point(
((maxSingleDigitSize.x + abs(aodTranslate.x)) * 2),
@@ -103,6 +117,11 @@
}
}
+ override fun onLocaleChanged(locale: Locale) {
+ updateLocale(locale)
+ requestLayout()
+ }
+
override fun animateDoze(isDozing: Boolean, isAnimated: Boolean) {
dozeControlState.animateDoze = {
super.animateDoze(isDozing, isAnimated)
@@ -163,6 +182,18 @@
}
}
+ private fun updateLocale(locale: Locale) {
+ isMonoVerticalNumericLineSpacing =
+ !NON_MONO_VERTICAL_NUMERIC_LINE_SPACING_LANGUAGES.any {
+ val newLocaleNumberFormat =
+ NumberFormat.getInstance(locale).format(FORMAT_NUMBER.toLong())
+ val nonMonoVerticalNumericLineSpaceNumberFormat =
+ NumberFormat.getInstance(Locale.forLanguageTag(it))
+ .format(FORMAT_NUMBER.toLong())
+ newLocaleNumberFormat == nonMonoVerticalNumericLineSpaceNumberFormat
+ }
+ }
+
/**
* Offsets the textViews of the clock for the step clock animation.
*
@@ -261,10 +292,18 @@
// Constants for the animation
private val MOVE_INTERPOLATOR = Interpolators.EMPHASIZED
+ private const val FORMAT_NUMBER = 1234567890
+
// Total available transition time for each digit, taking into account the step. If step is
// 0.1, then digit 0 would animate over 0.0 - 0.7, making availableTime 0.7.
private const val AVAILABLE_ANIMATION_TIME = 1.0f - MOVE_DIGIT_STEP * (NUM_DIGITS - 1)
+ // Add language tags below that do not have vertically mono spaced numerals
+ private val NON_MONO_VERTICAL_NUMERIC_LINE_SPACING_LANGUAGES =
+ setOf(
+ "my", // Burmese
+ )
+
// Use the sign of targetTranslation to control the direction of digit translation
fun updateDirectionalTargetTranslate(id: Int, targetTranslation: Point): Point {
val outPoint = Point(targetTranslation)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
index bd56dbf..48761c0 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
@@ -101,7 +101,7 @@
}
}
- override var verticalAlignment: VerticalAlignment = VerticalAlignment.CENTER
+ override var verticalAlignment: VerticalAlignment = VerticalAlignment.BASELINE
override var horizontalAlignment: HorizontalAlignment = HorizontalAlignment.LEFT
override var isAnimationEnabled = true
override var dozeFraction: Float = 0F
@@ -242,7 +242,7 @@
-translation.y.toFloat(),
(-translation.x + measuredWidth).toFloat(),
(-translation.y + measuredHeight).toFloat(),
- Paint().also { it.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT) },
+ PORTER_DUFF_XFER_MODE_PAINT,
)
canvas.restore()
canvas.restore()
@@ -387,7 +387,7 @@
// translation of reference point of text
// used for translation when calling textInterpolator
- fun getLocalTranslation(): Point {
+ private fun getLocalTranslation(): Point {
val viewHeight = if (isVertical) measuredWidth else measuredHeight
val interpolatedTextBounds = updateInterpolatedTextBounds()
val localTranslation = Point(0, 0)
@@ -413,7 +413,9 @@
correctedBaseline
}
VerticalAlignment.BASELINE -> {
- localTranslation.y = -lockScreenPaint.strokeWidth.toInt()
+ // account for max bottom distance of font, so clock doesn't collide with elements
+ localTranslation.y =
+ -lockScreenPaint.strokeWidth.toInt() - paint.fontMetrics.descent.toInt()
}
}
@@ -533,7 +535,9 @@
}
companion object {
- val AOD_STROKE_WIDTH = "2dp"
+ private val PORTER_DUFF_XFER_MODE_PAINT =
+ Paint().also { it.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT) }
+
val AOD_COLOR = Color.WHITE
val OPTICAL_SIZE_AXIS = ClockFontAxisSetting("opsz", 144f)
val DEFAULT_LS_VARIATION =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
index baeb2dd..0084e18 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
@@ -16,38 +16,64 @@
package com.android.systemui.communal.ui.viewmodel
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class CommunalTransitionViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class CommunalTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private val communalSceneRepository = kosmos.fakeCommunalSceneRepository
+ private val sceneInteractor = kosmos.sceneInteractor
private val underTest: CommunalTransitionViewModel by lazy {
kosmos.communalTransitionViewModel
}
+ @DisableFlags(FLAG_SCENE_CONTAINER)
@Test
fun testIsUmoOnCommunalDuringTransitionBetweenLockscreenAndGlanceableHub() =
testScope.runTest {
@@ -61,6 +87,7 @@
assertThat(isUmoOnCommunal).isFalse()
}
+ @DisableFlags(FLAG_SCENE_CONTAINER)
@Test
fun testIsUmoOnCommunalDuringTransitionBetweenDreamingAndGlanceableHub() =
testScope.runTest {
@@ -74,6 +101,7 @@
assertThat(isUmoOnCommunal).isFalse()
}
+ @DisableFlags(FLAG_SCENE_CONTAINER)
@Test
fun testIsUmoOnCommunalDuringTransitionBetweenOccludedAndGlanceableHub() =
testScope.runTest {
@@ -87,6 +115,7 @@
assertThat(isUmoOnCommunal).isFalse()
}
+ @DisableFlags(FLAG_SCENE_CONTAINER)
@Test
fun isUmoOnCommunal_noLongerVisible_returnsFalse() =
testScope.runTest {
@@ -103,6 +132,7 @@
assertThat(isUmoOnCommunal).isFalse()
}
+ @DisableFlags(FLAG_SCENE_CONTAINER)
@Test
fun isUmoOnCommunal_idleOnCommunal_returnsTrue() =
testScope.runTest {
@@ -116,11 +146,25 @@
assertThat(isUmoOnCommunal).isTrue()
}
+ @EnableFlags(FLAG_SCENE_CONTAINER)
+ @Test
+ fun isUmoOnCommunal_sceneContainerEnabled_idleOnCommunal_returnsTrue() =
+ testScope.runTest {
+ val isUmoOnCommunal by collectLastValue(underTest.isUmoOnCommunal)
+ assertThat(isUmoOnCommunal).isFalse()
+
+ // Change to communal scene.
+ setIdleScene(Scenes.Communal)
+
+ // isUmoOnCommunal returns true, even without any keyguard transition.
+ assertThat(isUmoOnCommunal).isTrue()
+ }
+
private suspend fun TestScope.enterCommunal(from: KeyguardState) {
keyguardTransitionRepository.sendTransitionSteps(
from = from,
to = KeyguardState.GLANCEABLE_HUB,
- testScope
+ testScope,
)
communalSceneRepository.changeScene(CommunalScenes.Communal)
runCurrent()
@@ -130,9 +174,17 @@
keyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.GLANCEABLE_HUB,
to = to,
- testScope
+ testScope,
)
communalSceneRepository.changeScene(CommunalScenes.Blank)
runCurrent()
}
+
+ private fun setIdleScene(scene: SceneKey) {
+ sceneInteractor.changeScene(scene, "test")
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(scene))
+ sceneInteractor.setTransitionState(transitionState)
+ testScope.runCurrent()
+ }
}
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 6b851cb..5bbd3ff 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
@@ -56,6 +56,7 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.media.controls.ui.controller.mediaCarouselController
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.testKosmos
@@ -133,6 +134,7 @@
accessibilityManager,
packageManager,
WIDGET_PICKER_PACKAGE_NAME,
+ kosmos.mediaCarouselController,
)
}
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 09daa51..3eba8ff 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
@@ -69,6 +69,7 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.controller.mediaCarouselController
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
@@ -178,6 +179,7 @@
mediaHost,
logcatLogBuffer("CommunalViewModelTest"),
metricsLogger,
+ kosmos.mediaCarouselController,
)
}
@@ -627,10 +629,7 @@
kosmos.setTransition(
sceneTransition = Idle(Scenes.Communal),
stateTransition =
- TransitionStep(
- from = KeyguardState.DREAMING,
- to = KeyguardState.GLANCEABLE_HUB
- ),
+ TransitionStep(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB),
)
// Then flow is not frozen
@@ -647,10 +646,7 @@
kosmos.setTransition(
sceneTransition = Idle(Scenes.Lockscreen),
stateTransition =
- TransitionStep(
- from = KeyguardState.GLANCEABLE_HUB,
- to = KeyguardState.OCCLUDED
- ),
+ TransitionStep(from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.OCCLUDED),
)
// Then flow is not frozen
@@ -735,10 +731,7 @@
kosmos.setTransition(
sceneTransition = Idle(Scenes.Communal),
stateTransition =
- TransitionStep(
- from = KeyguardState.DREAMING,
- to = KeyguardState.GLANCEABLE_HUB
- ),
+ TransitionStep(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB),
)
// Widgets available
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index d823bf5..4eef308 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -27,10 +27,14 @@
import android.platform.test.annotations.EnableFlags
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.table.logcatTableLogBuffer
import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
@@ -40,9 +44,7 @@
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.user.data.repository.userRepository
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.time.fakeSystemClock
import com.android.wifitrackerlib.HotspotNetworkEntry
import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType
@@ -53,31 +55,23 @@
import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE
import com.android.wifitrackerlib.WifiPickerTracker
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-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.mockito.kotlin.any
-import org.mockito.kotlin.capture
+import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.mock
-import org.mockito.kotlin.times
+import org.mockito.kotlin.never
+import org.mockito.kotlin.reset
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
-/**
- * Note: Most of these tests are duplicates of [WifiRepositoryImplTest] tests.
- *
- * Any new tests added here may also need to be added to [WifiRepositoryImplTest].
- */
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class WifiRepositoryImplTest() : SysuiTestCase() {
- private val kosmos = testKosmos()
+class WifiRepositoryImplTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val userRepository = kosmos.fakeUserRepository
// Using lazy means that the class will only be constructed once it's fetched. Because the
@@ -108,13 +102,13 @@
private val callbackCaptor = argumentCaptor<WifiPickerTracker.WifiPickerTrackerCallback>()
- private val dispatcher = StandardTestDispatcher()
- private val testScope = TestScope(dispatcher)
+ private val dispatcher = kosmos.testDispatcher
+ private val testScope = kosmos.testScope
@Before
fun setUp() {
userRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
- whenever(wifiPickerTrackerFactory.create(any(), any(), capture(callbackCaptor), any()))
+ whenever(wifiPickerTrackerFactory.create(any(), any(), callbackCaptor.capture(), any()))
.thenReturn(wifiPickerTracker)
}
@@ -122,7 +116,6 @@
fun wifiPickerTrackerCreation_scansDisabled() =
testScope.runTest {
collectLastValue(underTest.wifiNetwork)
- testScope.runCurrent()
verify(wifiPickerTracker).disableScanning()
}
@@ -1001,7 +994,6 @@
mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(false) }
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
getCallback().onWifiEntriesChanged()
- testScope.runCurrent()
assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
}
@@ -1017,7 +1009,6 @@
}
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
getCallback().onWifiEntriesChanged()
- testScope.runCurrent()
assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
}
@@ -1034,7 +1025,6 @@
}
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
getCallback().onWifiEntriesChanged()
- testScope.runCurrent()
assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
}
@@ -1051,7 +1041,6 @@
}
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
getCallback().onWifiEntriesChanged()
- testScope.runCurrent()
assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
}
@@ -1068,7 +1057,6 @@
}
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
getCallback().onWifiEntriesChanged()
- testScope.runCurrent()
assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
}
@@ -1085,7 +1073,6 @@
}
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
getCallback().onWifiEntriesChanged()
- testScope.runCurrent()
assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
}
@@ -1103,14 +1090,12 @@
}
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
getCallback().onWifiEntriesChanged()
- testScope.runCurrent()
assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
// WHEN the network is lost
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
getCallback().onWifiEntriesChanged()
- testScope.runCurrent()
// THEN the isWifiConnected updates
assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
@@ -1216,9 +1201,6 @@
assertThat(latest).isEmpty()
}
- // TODO(b/371586248): This test currently require currentUserContext to be public for testing,
- // this needs to
- // be updated to capture the argument instead so currentUserContext can be private.
@Test
@EnableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT)
fun oneUserVerifyCreatingWifiPickerTracker_multiuserFlagEnabled() =
@@ -1230,16 +1212,16 @@
)
userRepository.setSelectedUserInfo(PRIMARY_USER)
- runCurrent()
- val currentUserContext by collectLastValue(underTest.selectedUserContext)
- assertThat(currentUserContext).isEqualTo(primaryUserMockContext)
- verify(wifiPickerTrackerFactory).create(any(), any(), any(), any())
+ collectLastValue(underTest.wifiNetwork)
+
+ val contextCaptor = argumentCaptor<Context>()
+ verify(wifiPickerTrackerFactory).create(contextCaptor.capture(), any(), any(), any())
+ // If the flag is on, verify that we use the context from #createContextAsUser and we
+ // do NOT use the top-level context
+ assertThat(contextCaptor.firstValue).isEqualTo(primaryUserMockContext)
}
- // TODO(b/371586248): This test currently require currentUserContext to be public for testing,
- // this needs to
- // be updated to capture the argument instead so currentUserContext can be private.
@Test
@EnableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT)
fun changeUserVerifyCreatingWifiPickerTracker_multiuserEnabled() =
@@ -1249,32 +1231,30 @@
UserHandle.of(PRIMARY_USER_ID),
primaryUserMockContext,
)
-
- runCurrent()
userRepository.setSelectedUserInfo(PRIMARY_USER)
- runCurrent()
- val currentUserContext by collectLastValue(underTest.selectedUserContext)
- assertThat(currentUserContext).isEqualTo(primaryUserMockContext)
+ collectLastValue(underTest.wifiNetwork)
+ val contextCaptor = argumentCaptor<Context>()
+ verify(wifiPickerTrackerFactory).create(contextCaptor.capture(), any(), any(), any())
+ assertThat(contextCaptor.firstValue).isEqualTo(primaryUserMockContext)
+
+ reset(wifiPickerTrackerFactory)
+
+ // WHEN we switch to a different user
val otherUserMockContext = mock<Context>()
mContext.prepareCreateContextAsUser(
UserHandle.of(ANOTHER_USER_ID),
otherUserMockContext,
)
-
- runCurrent()
userRepository.setSelectedUserInfo(ANOTHER_USER)
- runCurrent()
- val otherUserContext by collectLastValue(underTest.selectedUserContext)
- assertThat(otherUserContext).isEqualTo(otherUserMockContext)
- verify(wifiPickerTrackerFactory, times(2)).create(any(), any(), any(), any())
+ // THEN we use the different user's context to create WifiPickerTracker
+ val newCaptor = argumentCaptor<Context>()
+ verify(wifiPickerTrackerFactory).create(newCaptor.capture(), any(), any(), any())
+ assertThat(newCaptor.firstValue).isEqualTo(otherUserMockContext)
}
- // TODO(b/371586248): This test currently require currentUserContext to be public for testing,
- // this needs to
- // be updated to capture the argument instead so currentUserContext can be private.
@Test
@DisableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT)
fun changeUserVerifyCreatingWifiPickerTracker_multiuserDisabled() =
@@ -1285,36 +1265,39 @@
primaryUserMockContext,
)
- runCurrent()
userRepository.setSelectedUserInfo(PRIMARY_USER)
- runCurrent()
- val currentUserContext by collectLastValue(underTest.selectedUserContext)
- assertThat(currentUserContext).isEqualTo(primaryUserMockContext)
+ collectLastValue(underTest.wifiNetwork)
+ val contextCaptor = argumentCaptor<Context>()
+ verify(wifiPickerTrackerFactory).create(contextCaptor.capture(), any(), any(), any())
+ // If the flag is off, verify that we do NOT use the context from #createContextAsUser
+ // and we instead use the top-level context
+ assertThat(contextCaptor.firstValue).isNotEqualTo(primaryUserMockContext)
+ assertThat(contextCaptor.firstValue).isEqualTo(mContext)
+
+ reset(wifiPickerTrackerFactory)
+
+ // WHEN we switch to a different user
val otherUserMockContext = mock<Context>()
mContext.prepareCreateContextAsUser(
UserHandle.of(ANOTHER_USER_ID),
otherUserMockContext,
)
-
- runCurrent()
userRepository.setSelectedUserInfo(ANOTHER_USER)
- runCurrent()
- verify(wifiPickerTrackerFactory, times(1)).create(any(), any(), any(), any())
+ // THEN we do NOT re-create WifiPickerTracker because the multiuser flag is off
+ verify(wifiPickerTrackerFactory, never()).create(any(), any(), any(), any())
}
private fun getCallback(): WifiPickerTracker.WifiPickerTrackerCallback {
- testScope.runCurrent()
- return callbackCaptor.value
+ return callbackCaptor.firstValue
}
private fun getTrafficStateCallback(): WifiManager.TrafficStateCallback {
- testScope.runCurrent()
val callbackCaptor = argumentCaptor<WifiManager.TrafficStateCallback>()
verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture())
- return callbackCaptor.value!!
+ return callbackCaptor.firstValue
}
private fun createHotspotWithType(@DeviceType type: Int): HotspotNetworkEntry {
@@ -1325,10 +1308,9 @@
}
private fun getScanResultsCallback(): WifiManager.ScanResultsCallback {
- testScope.runCurrent()
val callbackCaptor = argumentCaptor<WifiManager.ScanResultsCallback>()
verify(wifiManager).registerScanResultsCallback(any(), callbackCaptor.capture())
- return callbackCaptor.value!!
+ return callbackCaptor.firstValue
}
private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
index 0e32c95..e686ede 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
@@ -24,6 +24,7 @@
import android.provider.Settings
import android.service.notification.SystemZenRules
import android.service.notification.ZenModeConfig
+import android.service.notification.ZenModeConfig.ScheduleInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.notification.modes.TestModeBuilder
@@ -65,6 +66,7 @@
private lateinit var underTest: ModesDialogViewModel
private lateinit var timeScheduleMode: ZenMode
+ private lateinit var timeScheduleInfo: ScheduleInfo
@Before
fun setUp() {
@@ -78,18 +80,18 @@
kosmos.mockModesDialogEventLogger,
)
- val scheduleInfo = ZenModeConfig.ScheduleInfo()
- scheduleInfo.days = intArrayOf(Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY)
- scheduleInfo.startHour = 11
- scheduleInfo.endHour = 15
+ timeScheduleInfo = ZenModeConfig.ScheduleInfo()
+ timeScheduleInfo.days = intArrayOf(Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY)
+ timeScheduleInfo.startHour = 11
+ timeScheduleInfo.endHour = 15
timeScheduleMode =
TestModeBuilder()
.setPackage(SystemZenRules.PACKAGE_ANDROID)
.setType(AutomaticZenRule.TYPE_SCHEDULE_TIME)
.setManualInvocationAllowed(true)
- .setConditionId(ZenModeConfig.toScheduleConditionId(scheduleInfo))
+ .setConditionId(ZenModeConfig.toScheduleConditionId(timeScheduleInfo))
.setTriggerDescription(
- SystemZenRules.getTriggerDescriptionForScheduleTime(mContext, scheduleInfo)
+ SystemZenRules.getTriggerDescriptionForScheduleTime(mContext, timeScheduleInfo)
)
.build()
}
@@ -358,7 +360,7 @@
assertThat(tiles!![3].subtext).isEqualTo("Off")
assertThat(tiles!![4].subtext).isEqualTo("On")
assertThat(tiles!![5].subtext).isEqualTo("Not set")
- assertThat(tiles!![6].subtext).isEqualTo("Mon - Wed, 11:00 AM - 3:00 PM")
+ assertThat(tiles!![6].subtext).isEqualTo(timeScheduleMode.triggerDescription)
}
@Test
@@ -437,7 +439,8 @@
with(tiles?.elementAt(6)!!) {
assertThat(this.stateDescription).isEqualTo("Off")
assertThat(this.subtextDescription)
- .isEqualTo("Monday to Wednesday, 11:00 AM - 3:00 PM")
+ .isEqualTo(SystemZenRules.getDaysOfWeekFull(context, timeScheduleInfo)
+ + ", " + SystemZenRules.getTimeSummary(context, timeScheduleInfo))
}
// All tiles have the same long click info
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt
new file mode 100644
index 0000000..7a9d017
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.window
+
+import android.platform.test.annotations.EnableFlags
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class MultiDisplayStatusBarWindowControllerStoreTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val fakeDisplayRepository = kosmos.displayRepository
+ private val testScope = kosmos.testScope
+
+ private val underTest by lazy { kosmos.multiDisplayStatusBarWindowControllerStore }
+
+ @Before
+ fun start() {
+ underTest.start()
+ }
+
+ @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }
+
+ @Test
+ fun beforeDisplayRemoved_doesNotStopInstances() =
+ testScope.runTest {
+ val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+ verify(instance, never()).stop()
+ }
+
+ @Test
+ fun displayRemoved_stopsInstance() =
+ testScope.runTest {
+ val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+ fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+
+ verify(instance).stop()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt
index 6e66287..61c7193 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt
@@ -17,12 +17,17 @@
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.view.fakeWindowManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.fragments.fragmentService
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.policy.statusBarConfigurationController
import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
@@ -37,6 +42,9 @@
testKosmos().also { it.statusBarWindowViewInflater = it.fakeStatusBarWindowViewInflater }
private val underTest = kosmos.statusBarWindowControllerImpl
+ private val fakeExecutor = kosmos.fakeExecutor
+ private val fakeWindowManager = kosmos.fakeWindowManager
+ private val mockFragmentService = kosmos.fragmentService
private val fakeStatusBarWindowViewInflater = kosmos.fakeStatusBarWindowViewInflater
private val statusBarConfigurationController = kosmos.statusBarConfigurationController
@@ -59,4 +67,64 @@
verify(mockWindowView, never()).setStatusBarConfigurationController(any())
}
+
+ @Test
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarConnectedDisplays.FLAG_NAME)
+ fun stop_statusBarModernizationFlagEnabled_doesNotRemoveFragment() {
+ val windowView = fakeStatusBarWindowViewInflater.inflatedMockViews.first()
+
+ underTest.stop()
+ fakeExecutor.runAllReady()
+
+ verify(mockFragmentService, never()).removeAndDestroy(windowView)
+ }
+
+ @Test
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun stop_statusBarModernizationFlagDisabled_removesFragment() {
+ val windowView = fakeStatusBarWindowViewInflater.inflatedMockViews.first()
+
+ underTest.stop()
+ fakeExecutor.runAllReady()
+
+ verify(mockFragmentService).removeAndDestroy(windowView)
+ }
+
+ @Test
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun stop_statusBarModernizationFlagDisabled_removesFragmentOnExecutor() {
+ val windowView = fakeStatusBarWindowViewInflater.inflatedMockViews.first()
+
+ underTest.stop()
+
+ verify(mockFragmentService, never()).removeAndDestroy(windowView)
+ fakeExecutor.runAllReady()
+ verify(mockFragmentService).removeAndDestroy(windowView)
+ }
+
+ @Test
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun stop_removesWindowViewFromWindowManager() {
+ underTest.attach()
+ underTest.stop()
+
+ assertThat(fakeWindowManager.addedViews).isEmpty()
+ }
+
+ @Test(expected = IllegalStateException::class)
+ @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun stop_connectedDisplaysFlagDisabled_crashes() {
+ underTest.stop()
+ }
+
+ @Test
+ fun attach_windowViewAddedToWindowManager() {
+ val windowView = fakeStatusBarWindowViewInflater.inflatedMockViews.first()
+
+ underTest.attach()
+
+ assertThat(fakeWindowManager.addedViews.keys).containsExactly(windowView)
+ }
}
diff --git a/packages/SystemUI/res/drawable/volume_background_top.xml b/packages/SystemUI/res/drawable/volume_background_top.xml
index 75849be..132572a 100644
--- a/packages/SystemUI/res/drawable/volume_background_top.xml
+++ b/packages/SystemUI/res/drawable/volume_background_top.xml
@@ -17,7 +17,6 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<item>
<shape>
- <size android:width="@dimen/volume_dialog_width" />
<solid android:color="?androidprv:attr/materialColorSurface" />
<corners android:topLeftRadius="@dimen/volume_dialog_background_corner_radius"
android:topRightRadius="@dimen/volume_dialog_background_corner_radius"/>
diff --git a/packages/SystemUI/res/drawable/volume_dialog_spacer.xml b/packages/SystemUI/res/drawable/volume_dialog_spacer.xml
deleted file mode 100644
index 3c60784..0000000
--- a/packages/SystemUI/res/drawable/volume_dialog_spacer.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- 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.
--->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <size
- android:width="@dimen/volume_dialog_spacing"
- android:height="@dimen/volume_dialog_spacing" />
- <solid android:color="@color/transparent" />
-</shape>
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index f187ce6..694357d 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -13,58 +13,70 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/volume_dialog_root"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ app:layoutDescription="@xml/volume_dialog_scene">
- <LinearLayout
- android:id="@+id/volume_dialog_container"
+ <View
+ android:id="@+id/volume_dialog_background"
+ android:layout_width="@dimen/volume_dialog_width"
+ android:layout_height="0dp"
+ android:layout_marginTop="@dimen/volume_dialog_background_vertical_margin"
+ android:layout_marginBottom="@dimen/volume_dialog_background_vertical_margin"
+ android:background="@drawable/volume_dialog_background"
+ app:layout_constraintBottom_toBottomOf="@id/volume_dialog_settings"
+ app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container"
+ app:layout_constraintStart_toStartOf="@id/volume_dialog_main_slider_container"
+ app:layout_constraintTop_toTopOf="@id/volume_ringer_and_drawer_container" />
+
+ <include
+ android:id="@id/volume_ringer_and_drawer_container"
+ layout="@layout/volume_ringer_drawer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical|end"
+ android:layout_marginBottom="@dimen/volume_dialog_components_spacing"
+ app:layout_constraintBottom_toTopOf="@id/volume_dialog_main_slider_container"
+ app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container"
+ app:layout_constraintStart_toStartOf="@id/volume_dialog_main_slider_container"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="1" />
+
+ <include
+ android:id="@+id/volume_dialog_main_slider_container"
+ layout="@layout/volume_dialog_slider" />
+
+ <ImageButton
+ android:id="@+id/volume_dialog_settings"
+ android:layout_width="@dimen/volume_dialog_button_size"
+ android:layout_height="@dimen/volume_dialog_button_size"
+ android:layout_marginTop="@dimen/volume_dialog_components_spacing"
+ android:background="@drawable/ripple_drawable_20dp"
+ android:contentDescription="@string/accessibility_volume_settings"
+ android:soundEffectsEnabled="false"
+ android:src="@drawable/horizontal_ellipsis"
+ android:tint="?androidprv:attr/materialColorPrimary"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container"
+ app:layout_constraintStart_toStartOf="@id/volume_dialog_main_slider_container"
+ app:layout_constraintTop_toBottomOf="@id/volume_dialog_main_slider_container"
+ app:layout_constraintVertical_bias="0" />
+
+ <LinearLayout
+ android:id="@+id/volume_dialog_floating_sliders_container"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_marginTop="@dimen/volume_dialog_floating_sliders_vertical_padding_negative"
+ android:layout_marginBottom="@dimen/volume_dialog_floating_sliders_vertical_padding_negative"
android:divider="@drawable/volume_dialog_floating_sliders_spacer"
+ android:gravity="bottom"
android:orientation="horizontal"
- android:showDividers="middle|end|beginning">
+ android:showDividers="middle|beginning|end"
+ app:layout_constraintBottom_toBottomOf="@id/volume_dialog_main_slider_container"
+ app:layout_constraintEnd_toStartOf="@id/volume_dialog_background"
+ app:layout_constraintTop_toTopOf="@id/volume_dialog_main_slider_container" />
- <LinearLayout
- android:id="@+id/volume_dialog_floating_sliders_container"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:divider="@drawable/volume_dialog_floating_sliders_spacer"
- android:gravity="bottom"
- android:orientation="horizontal"
- android:paddingBottom="@dimen/volume_dialog_floating_sliders_bottom_padding"
- android:showDividers="middle" />
-
- <LinearLayout
- android:id="@+id/volume_dialog"
- android:layout_width="@dimen/volume_dialog_width"
- android:layout_height="wrap_content"
- android:background="@drawable/volume_dialog_background"
- android:clipChildren="false"
- android:clipToOutline="false"
- android:clipToPadding="false"
- android:divider="@drawable/volume_dialog_spacer"
- android:gravity="center_horizontal"
- android:orientation="vertical"
- android:paddingVertical="@dimen/volume_dialog_vertical_padding"
- android:showDividers="middle">
-
- <include layout="@layout/volume_ringer_drawer" />
-
- <include layout="@layout/volume_dialog_slider" />
-
- <ImageButton
- android:id="@+id/volume_dialog_settings"
- android:layout_width="@dimen/volume_dialog_button_size"
- android:layout_height="@dimen/volume_dialog_button_size"
- android:background="@drawable/ripple_drawable_20dp"
- android:contentDescription="@string/accessibility_volume_settings"
- android:soundEffectsEnabled="false"
- android:src="@drawable/horizontal_ellipsis"
- android:tint="?androidprv:attr/materialColorPrimary" />
- </LinearLayout>
- </LinearLayout>
-</FrameLayout>
\ No newline at end of file
+</androidx.constraintlayout.motion.widget.MotionLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
index 7c266e6..b71c470 100644
--- a/packages/SystemUI/res/layout/volume_ringer_drawer.xml
+++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
@@ -19,13 +19,10 @@
android:id="@+id/volume_ringer_and_drawer_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="center"
- android:paddingLeft="@dimen/volume_dialog_ringer_horizontal_padding"
- android:paddingRight="@dimen/volume_dialog_ringer_horizontal_padding"
- android:layoutDirection="ltr"
- android:clipToPadding="false"
android:clipChildren="false"
- android:background="@drawable/volume_background_top">
+ android:clipToPadding="false"
+ android:gravity="center"
+ android:layoutDirection="ltr">
<!-- Drawer view, invisible by default. -->
<FrameLayout
@@ -37,10 +34,10 @@
<!-- View that is animated to a tapped ringer selection, so it appears selected. -->
<FrameLayout
android:id="@+id/volume_drawer_selection_background"
- android:alpha="0.0"
android:layout_width="@dimen/volume_dialog_ringer_drawer_button_size"
android:layout_height="@dimen/volume_dialog_ringer_drawer_button_size"
android:layout_gravity="bottom|right"
+ android:alpha="0.0"
android:background="@drawable/volume_drawer_selection_bg" />
<LinearLayout
@@ -65,7 +62,6 @@
android:background="@drawable/volume_drawer_selection_bg"
android:contentDescription="@string/volume_ringer_change"
android:gravity="center"
- android:padding="@dimen/volume_dialog_ringer_horizontal_padding"
android:src="@drawable/ic_volume_media"
android:tint="?androidprv:attr/materialColorOnPrimary" />
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index 4a53df9..75bee9f 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -46,8 +46,6 @@
<dimen name="lockscreen_shade_max_over_scroll_amount">32dp</dimen>
- <dimen name="status_view_margin_horizontal">8dp</dimen>
-
<!-- Lockscreen shade transition values -->
<dimen name="lockscreen_shade_transition_by_tap_distance">200dp</dimen>
<dimen name="lockscreen_shade_full_transition_distance">200dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp-port/dimens.xml b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
index 707bc9e..f73e91a 100644
--- a/packages/SystemUI/res/values-sw600dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
@@ -16,8 +16,6 @@
-->
<resources>
<dimen name="notification_panel_margin_horizontal">48dp</dimen>
- <dimen name="status_view_margin_horizontal">62dp</dimen>
-
<!-- qs_tiles_page_horizontal_margin should be margin / 2, otherwise full space between two
pages is margin * 2, and that makes tiles page not appear immediately after user swipes to
the side -->
diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
index 8583f05..41bb37e 100644
--- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -22,8 +22,6 @@
<dimen name="keyguard_split_shade_top_margin">72dp</dimen>
- <dimen name="status_view_margin_horizontal">24dp</dimen>
-
<dimen name="qs_media_session_height_expanded">184dp</dimen>
<dimen name="qs_content_horizontal_padding">40dp</dimen>
<dimen name="qs_horizontal_margin">40dp</dimen>
diff --git a/packages/SystemUI/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
index 9248d58..eb570b8 100644
--- a/packages/SystemUI/res/values-sw720dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
@@ -20,8 +20,6 @@
<!-- These resources are around just to allow their values to be customized
for different hardware and product builds. -->
<resources>
- <dimen name="status_view_margin_horizontal">124dp</dimen>
-
<dimen name="large_screen_shade_header_left_padding">24dp</dimen>
<dimen name="qqs_layout_padding_bottom">40dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7af0057..4954f91 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1955,8 +1955,6 @@
<dimen name="dream_overlay_entry_y_offset">40dp</dimen>
<dimen name="dream_overlay_exit_y_offset">40dp</dimen>
- <dimen name="status_view_margin_horizontal">0dp</dimen>
-
<!-- Media output broadcast dialog QR code picture size -->
<dimen name="media_output_qrcode_size">216dp</dimen>
<dimen name="media_output_broadcast_info">21dp</dimen>
@@ -2060,26 +2058,29 @@
<dimen name="contextual_edu_dialog_elevation">2dp</dimen>
<!-- Volume start -->
- <dimen name="volume_dialog_background_corner_radius">30dp</dimen>
<dimen name="volume_dialog_width">60dp</dimen>
- <dimen name="volume_dialog_vertical_padding">10dp</dimen>
+
+ <dimen name="volume_dialog_background_corner_radius">30dp</dimen>
+ <dimen name="volume_dialog_background_vertical_margin">-10dp</dimen>
+
<dimen name="volume_dialog_components_spacing">8dp</dimen>
<dimen name="volume_dialog_floating_sliders_spacing">8dp</dimen>
<dimen name="volume_dialog_floating_sliders_vertical_padding">10dp</dimen>
+ <dimen name="volume_dialog_floating_sliders_vertical_padding_negative">-10dp</dimen>
<dimen name="volume_dialog_floating_sliders_horizontal_padding">4dp</dimen>
- <dimen name="volume_dialog_spacing">4dp</dimen>
<dimen name="volume_dialog_button_size">48dp</dimen>
- <dimen name="volume_dialog_floating_sliders_bottom_padding">48dp</dimen>
<dimen name="volume_dialog_slider_width">52dp</dimen>
<dimen name="volume_dialog_slider_height">254dp</dimen>
- <dimen name="volume_panel_slice_vertical_padding">8dp</dimen>
- <dimen name="volume_panel_slice_horizontal_padding">24dp</dimen>
+ <fraction name="volume_dialog_half_opened_bias">0.2</fraction>
- <dimen name="volume_dialog_ringer_horizontal_padding">10dp</dimen>
+ <dimen name="volume_dialog_background_square_corner_radius">12dp</dimen>
+
<dimen name="volume_dialog_ringer_drawer_button_size">40dp</dimen>
<dimen name="volume_dialog_ringer_drawer_button_icon_radius">10dp</dimen>
- <dimen name="volume_dialog_background_square_corner_radius">12dp</dimen>
<dimen name="volume_dialog_ringer_selected_button_background_radius">20dp</dimen>
+
+ <dimen name="volume_panel_slice_vertical_padding">8dp</dimen>
+ <dimen name="volume_panel_slice_horizontal_padding">24dp</dimen>
<!-- Volume end -->
</resources>
diff --git a/packages/SystemUI/res/xml/volume_dialog_constraint_set.xml b/packages/SystemUI/res/xml/volume_dialog_constraint_set.xml
new file mode 100644
index 0000000..9018e5b
--- /dev/null
+++ b/packages/SystemUI/res/xml/volume_dialog_constraint_set.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ConstraintSet xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/volume_dialog_constraint_set">
+
+ <Constraint
+ android:id="@id/volume_dialog_main_slider_container"
+ android:layout_width="@dimen/volume_dialog_slider_width"
+ android:layout_height="@dimen/volume_dialog_slider_height"
+ android:layout_marginEnd="@dimen/volume_dialog_components_spacing"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="0.5" />
+</ConstraintSet>
\ No newline at end of file
diff --git a/packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml b/packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml
new file mode 100644
index 0000000..297c388
--- /dev/null
+++ b/packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ConstraintSet xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/volume_dialog_half_folded_constraint_set">
+
+ <Constraint
+ android:id="@id/volume_dialog_main_slider_container"
+ android:layout_width="@dimen/volume_dialog_slider_width"
+ android:layout_height="@dimen/volume_dialog_slider_height"
+ android:layout_marginEnd="@dimen/volume_dialog_components_spacing"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="@fraction/volume_dialog_half_opened_bias" />
+</ConstraintSet>
\ No newline at end of file
diff --git a/packages/SystemUI/res/xml/volume_dialog_scene.xml b/packages/SystemUI/res/xml/volume_dialog_scene.xml
new file mode 100644
index 0000000..b813474
--- /dev/null
+++ b/packages/SystemUI/res/xml/volume_dialog_scene.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+
+<MotionScene xmlns:motion="http://schemas.android.com/apk/res-auto">
+
+ <Transition
+ motion:autoTransition="none"
+ motion:constraintSetEnd="@id/volume_dialog_half_folded_constraint_set"
+ motion:constraintSetStart="@id/volume_dialog_constraint_set"
+ motion:duration="150" />
+
+ <Include motion:constraintSet="@xml/volume_dialog_constraint_set" />
+ <Include motion:constraintSet="@xml/volume_dialog_half_folded_constraint_set" />
+</MotionScene>
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
index fb00d6e..db4d613 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
@@ -52,6 +52,8 @@
import com.google.common.util.concurrent.ListenableFuture;
+import kotlin.Unit;
+
import kotlinx.coroutines.Job;
import java.util.Collection;
@@ -87,7 +89,7 @@
private final ConfigurationInteractor mConfigurationInteractor;
private final Lifecycle mLifecycle;
- private Rect mExclusionRect = null;
+ private Rect mExclusionRect = new Rect();
private ISystemGestureExclusionListener mGestureExclusionListener;
@@ -298,9 +300,18 @@
public void onSystemGestureExclusionChanged(int displayId,
Region systemGestureExclusion,
Region systemGestureExclusionUnrestricted) {
- mExclusionRect = systemGestureExclusion.getBounds();
+ final Rect bounds = systemGestureExclusion.getBounds();
+ if (!mExclusionRect.equals(bounds)) {
+ mExclusionRect = bounds;
+ mLogger.i(msg -> "Exclusion rect updated to " + msg.getStr1(),
+ msg -> {
+ msg.setStr1(bounds.toString());
+ return Unit.INSTANCE;
+ });
+ }
}
};
+ mLogger.i("Registering system gesture exclusion listener");
mWindowManagerService.registerSystemGestureExclusionListener(
mGestureExclusionListener, mDisplayId);
} catch (RemoteException e) {
@@ -320,11 +331,12 @@
* Destroys any active {@link InputSession}.
*/
private void stopMonitoring(boolean force) {
- mExclusionRect = null;
+ mExclusionRect = new Rect();
if (bouncerAreaExclusion()) {
mBackgroundExecutor.execute(() -> {
try {
if (mGestureExclusionListener != null) {
+ mLogger.i("Unregistering system gesture exclusion listener");
mWindowManagerService.unregisterSystemGestureExclusionListener(
mGestureExclusionListener, mDisplayId);
mGestureExclusionListener = null;
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 4e0e112..5ecf2e6b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -29,6 +29,7 @@
import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.media.controls.ui.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
@@ -43,6 +44,7 @@
val communalSceneInteractor: CommunalSceneInteractor,
private val communalInteractor: CommunalInteractor,
val mediaHost: MediaHost,
+ val mediaCarouselController: MediaCarouselController,
) {
val currentScene: Flow<SceneKey> = communalSceneInteractor.currentScene
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 ccff230..736ed5c 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
@@ -46,6 +46,7 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.media.controls.ui.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.dagger.MediaModule
import com.android.systemui.res.R
@@ -82,7 +83,14 @@
private val accessibilityManager: AccessibilityManager,
private val packageManager: PackageManager,
@Named(LAUNCHER_PACKAGE) private val launcherPackage: String,
-) : BaseCommunalViewModel(communalSceneInteractor, communalInteractor, mediaHost) {
+ mediaCarouselController: MediaCarouselController,
+) :
+ BaseCommunalViewModel(
+ communalSceneInteractor,
+ communalInteractor,
+ mediaHost,
+ mediaCarouselController,
+ ) {
private val logger = Logger(logBuffer, "CommunalEditModeViewModel")
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
index 6239373..3496230 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
@@ -31,6 +31,7 @@
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
@@ -41,6 +42,7 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
@@ -60,7 +62,7 @@
glanceableHubToDreamTransitionViewModel: GlanceableHubToDreamingTransitionViewModel,
communalInteractor: CommunalInteractor,
private val communalSceneInteractor: CommunalSceneInteractor,
- keyguardTransitionInteractor: KeyguardTransitionInteractor
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
/**
* Snaps to [CommunalScenes.Communal], showing the glanceable hub immediately without any
@@ -89,7 +91,7 @@
keyguardTransitionInteractor
.transition(
edge = Edge.create(from = Scenes.Communal),
- edgeWithoutSceneContainer = Edge.create(from = KeyguardState.GLANCEABLE_HUB)
+ edgeWithoutSceneContainer = Edge.create(from = KeyguardState.GLANCEABLE_HUB),
)
.filter {
it.to == KeyguardState.OCCLUDED &&
@@ -114,21 +116,24 @@
// from
// the hub, then pressing power twice to go back to the lock screen.
communalSceneInteractor.isCommunalVisible,
- merge(
- lockscreenToGlanceableHubTransitionViewModel.showUmo,
- glanceableHubToLockscreenTransitionViewModel.showUmo,
- dreamToGlanceableHubTransitionViewModel.showUmo,
- glanceableHubToDreamTransitionViewModel.showUmo,
- showUmoFromOccludedToGlanceableHub,
- showUmoFromGlanceableHubToOccluded,
- )
- .onStart { emit(false) }
- )
+ // TODO(b/378942852): polish UMO transitions when scene container is enabled
+ if (SceneContainerFlag.isEnabled) flowOf(true)
+ else
+ merge(
+ lockscreenToGlanceableHubTransitionViewModel.showUmo,
+ glanceableHubToLockscreenTransitionViewModel.showUmo,
+ dreamToGlanceableHubTransitionViewModel.showUmo,
+ glanceableHubToDreamTransitionViewModel.showUmo,
+ showUmoFromOccludedToGlanceableHub,
+ showUmoFromGlanceableHubToOccluded,
+ )
+ .onStart { emit(false) },
+ ),
)
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = false
+ initialValue = false,
)
/** Whether to show communal when exiting the occluded state. */
@@ -145,8 +150,7 @@
val recentsBackgroundColor: Flow<Color?> =
combine(showCommunalFromOccluded, communalColors.backgroundColor) {
showCommunalFromOccluded,
- backgroundColor,
- ->
+ backgroundColor ->
if (showCommunalFromOccluded) {
backgroundColor
} else {
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 7990450..9cd6465 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
@@ -21,6 +21,7 @@
import android.os.Bundle
import android.view.View
import android.view.accessibility.AccessibilityNodeInfo
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
@@ -38,6 +39,7 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.media.controls.ui.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.ui.view.MediaHostState
@@ -72,7 +74,6 @@
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
/** The default view model used for showing the communal hub. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -95,7 +96,14 @@
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
@CommunalLog logBuffer: LogBuffer,
private val metricsLogger: CommunalMetricsLogger,
-) : BaseCommunalViewModel(communalSceneInteractor, communalInteractor, mediaHost) {
+ mediaCarouselController: MediaCarouselController,
+) :
+ BaseCommunalViewModel(
+ communalSceneInteractor,
+ communalInteractor,
+ mediaHost,
+ mediaCarouselController,
+ ) {
private val logger = Logger(logBuffer, "CommunalViewModel")
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 41e6929..3666de4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -72,6 +72,7 @@
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -90,6 +91,7 @@
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onKeyEvent
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@@ -99,6 +101,7 @@
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
+import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -377,15 +380,19 @@
Column(modifier = modifier.fillMaxSize().padding(horizontal = 24.dp)) {
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
- Box(modifier = Modifier.padding(start = 202.dp).width(412.dp)) {
- TitleBar(isCustomizing)
- }
- Spacer(modifier = Modifier.weight(1f))
- if (isShortcutCustomizerFlagEnabled) {
- if (isCustomizing) {
- DoneButton(onClick = { isCustomizing = false })
+ // Keep title centered whether customize button is visible or not.
+ Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) {
+ Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
+ TitleBar(isCustomizing)
+ }
+ if (isShortcutCustomizerFlagEnabled) {
+ if (isCustomizing) {
+ DoneButton(onClick = { isCustomizing = false })
+ } else {
+ CustomizeButton(onClick = { isCustomizing = true })
+ }
} else {
- CustomizeButton(onClick = { isCustomizing = true })
+ Spacer(modifier = Modifier.width(if (isCustomizing) 69.dp else 133.dp))
}
}
}
@@ -550,7 +557,7 @@
.padding(8.dp)
) {
Row(
- modifier = Modifier.width(128.dp).align(Alignment.CenterVertically),
+ modifier = Modifier.width(128.dp).align(Alignment.CenterVertically).weight(0.333f),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
@@ -561,7 +568,7 @@
}
Spacer(modifier = Modifier.width(24.dp))
ShortcutKeyCombinations(
- modifier = Modifier.weight(1f),
+ modifier = Modifier.weight(.666f),
shortcut = shortcut,
isCustomizing = isCustomizing,
onAddShortcutRequested = { onCustomizationRequested(shortcut.label) },
@@ -791,16 +798,25 @@
selectedCategory: ShortcutCategoryType?,
onCategoryClicked: (ShortcutCategoryUi) -> Unit,
) {
- Column(modifier) {
- ShortcutsSearchBar(onSearchQueryChanged)
- Spacer(modifier = Modifier.heightIn(8.dp))
- CategoriesPanelTwoPane(categories, selectedCategory, onCategoryClicked)
- Spacer(modifier = Modifier.weight(1f))
- KeyboardSettings(
- horizontalPadding = 24.dp,
- verticalPadding = 24.dp,
- onKeyboardSettingsClicked,
- )
+ CompositionLocalProvider(
+ // Restrict system font scale increases up to a max so categories display correctly.
+ LocalDensity provides
+ Density(
+ density = LocalDensity.current.density,
+ fontScale = LocalDensity.current.fontScale.coerceIn(1f, 1.5f),
+ )
+ ) {
+ Column(modifier) {
+ ShortcutsSearchBar(onSearchQueryChanged)
+ Spacer(modifier = Modifier.heightIn(8.dp))
+ CategoriesPanelTwoPane(categories, selectedCategory, onCategoryClicked)
+ Spacer(modifier = Modifier.weight(1f))
+ KeyboardSettings(
+ horizontalPadding = 24.dp,
+ verticalPadding = 24.dp,
+ onKeyboardSettingsClicked,
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelperUtils.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelperUtils.kt
index e295564..f9904f6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelperUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelperUtils.kt
@@ -49,5 +49,5 @@
object ShortcutHelperBottomSheet {
val DefaultWidth = 412.dp
val LargeScreenWidthPortrait = 704.dp
- val LargeScreenWidthLandscape = 864.dp
+ val LargeScreenWidthLandscape = 960.dp
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 2ee9ddb..01ec4d0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2875,7 +2875,7 @@
}
if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
- mKeyguardTransitions.startKeyguardTransition(showing, aodShowing);
+ startKeyguardTransition(showing, aodShowing);
} else {
try {
@@ -3019,7 +3019,7 @@
final int keyguardFlag = flags;
mUiBgExecutor.execute(() -> {
if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
- mKeyguardTransitions.startKeyguardTransition(
+ startKeyguardTransition(
false /* keyguardShowing */, false /* aodShowing */);
return;
}
@@ -3035,6 +3035,10 @@
}
};
+ private void startKeyguardTransition(boolean keyguardShowing, boolean aodShowing) {
+ mKeyguardTransitions.startKeyguardTransition(keyguardShowing, aodShowing);
+ }
+
private final Runnable mHideAnimationFinishedRunnable = () -> {
Log.e(TAG, "mHideAnimationFinishedRunnable#run");
mHideAnimationRunning = false;
@@ -3490,8 +3494,7 @@
mSurfaceBehindRemoteAnimationRequested = true;
if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
- mKeyguardTransitions.startKeyguardTransition(
- false /* keyguardShowing */, false /* aodShowing */);
+ startKeyguardTransition(false /* keyguardShowing */, false /* aodShowing */);
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
index 2914cb9..a137d6c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -122,10 +122,7 @@
if (visible) {
if (enableNewKeyguardShellTransitions) {
- keyguardTransitions.startKeyguardTransition(
- false /* keyguardShowing */,
- false, /* aodShowing */
- )
+ startKeyguardTransition(false, /* keyguardShowing */ false /* aodShowing */)
isKeyguardGoingAway = true
return
}
@@ -233,7 +230,7 @@
"aodVisible=$aodVisible).",
)
if (enableNewKeyguardShellTransitions) {
- keyguardTransitions.startKeyguardTransition(lockscreenShowing, aodVisible)
+ startKeyguardTransition(lockscreenShowing, aodVisible)
} else {
activityTaskManagerService.setLockScreenShown(lockscreenShowing, aodVisible)
}
@@ -241,6 +238,10 @@
this.isAodVisible = aodVisible
}
+ private fun startKeyguardTransition(keyguardShowing: Boolean, aodShowing: Boolean) {
+ keyguardTransitions.startKeyguardTransition(keyguardShowing, aodShowing)
+ }
+
private fun endKeyguardGoingAwayAnimation() {
if (!isKeyguardGoingAway) {
Log.d(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
index 914fdd2..6c03b24 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
@@ -151,7 +151,5 @@
cs.applyTo(rootView)
}
- private const val DATE_WEATHER_VIEW_HEIGHT = "date_weather_view_height"
- private const val ENHANCED_SMARTSPACE_HEIGHT = "enhanced_smartspace_height"
private const val TAG = "KeyguardPreviewClockViewBinder"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index 08c3f15..4c23adf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -29,6 +29,7 @@
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.customization.R as customR
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
@@ -114,14 +115,14 @@
START,
PARENT_ID,
START,
- context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal),
+ context.resources.getDimensionPixelSize(customR.dimen.status_view_margin_horizontal),
)
connect(
nicId,
END,
PARENT_ID,
END,
- context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal),
+ context.resources.getDimensionPixelSize(customR.dimen.status_view_margin_horizontal),
)
constrainHeight(
nicId,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index c009720..6c98d5b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -221,7 +221,9 @@
PARENT_ID,
START,
context.resources.getDimensionPixelSize(customR.dimen.clock_padding_start) +
- context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal),
+ context.resources.getDimensionPixelSize(
+ customR.dimen.status_view_margin_horizontal
+ ),
)
val smallClockTopMargin = keyguardClockViewModel.getSmallClockTopMargin()
create(R.id.small_clock_guideline_top, ConstraintSet.HORIZONTAL_GUIDELINE)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
index 0782846..f0d21f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
@@ -28,6 +28,7 @@
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
+import com.android.systemui.customization.R as customR
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.media.controls.ui.controller.KeyguardMediaController
@@ -42,7 +43,7 @@
constructor(
@ShadeDisplayAware private val context: Context,
private val notificationPanelView: NotificationPanelView,
- private val keyguardMediaController: KeyguardMediaController
+ private val keyguardMediaController: KeyguardMediaController,
) : KeyguardSection() {
private val mediaContainerId = R.id.status_view_media_container
@@ -62,7 +63,7 @@
val horizontalPadding =
padding +
context.resources.getDimensionPixelSize(
- R.dimen.status_view_margin_horizontal
+ customR.dimen.status_view_margin_horizontal
)
setPaddingRelative(horizontalPadding, padding, horizontalPadding, padding)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
index e30ddc6..de0927e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.content.Context
+import com.android.systemui.customization.R as customR
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor
@@ -59,10 +60,9 @@
/** Whether the weather area should be visible. */
val isWeatherVisible: StateFlow<Boolean> =
- combine(
+ combine(isWeatherEnabled, keyguardClockViewModel.hasCustomWeatherDataDisplay) {
isWeatherEnabled,
- keyguardClockViewModel.hasCustomWeatherDataDisplay,
- ) { isWeatherEnabled, clockIncludesCustomWeatherDisplay ->
+ clockIncludesCustomWeatherDisplay ->
isWeatherVisible(
clockIncludesCustomWeatherDisplay = clockIncludesCustomWeatherDisplay,
isWeatherEnabled = isWeatherEnabled,
@@ -76,7 +76,7 @@
clockIncludesCustomWeatherDisplay =
keyguardClockViewModel.hasCustomWeatherDataDisplay.value,
isWeatherEnabled = smartspaceInteractor.isWeatherEnabled.value,
- )
+ ),
)
private fun isWeatherVisible(
@@ -92,12 +92,12 @@
companion object {
fun getSmartspaceStartMargin(context: Context): Int {
return context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start) +
- context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+ context.resources.getDimensionPixelSize(customR.dimen.status_view_margin_horizontal)
}
fun getSmartspaceEndMargin(context: Context): Int {
return context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_end) +
- context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+ context.resources.getDimensionPixelSize(customR.dimen.status_view_margin_horizontal)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
index 0e09ad2..dbad602 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.panels.ui.compose.infinitegrid
import android.graphics.drawable.Animatable
+import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
import android.text.TextUtils
import androidx.compose.animation.animateColorAsState
@@ -228,7 +229,14 @@
}
}
}
- is Icon.Loaded -> rememberDrawablePainter(loadedDrawable)
+ is Icon.Loaded -> {
+ LaunchedEffect(loadedDrawable) {
+ if (loadedDrawable is AnimatedVectorDrawable) {
+ loadedDrawable.forceAnimationOnUI()
+ }
+ }
+ rememberDrawablePainter(loadedDrawable)
+ }
}
Image(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 0e82bf8..4ccd2b9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -3109,12 +3109,18 @@
if (isTracking()) {
onTrackingStopped(true);
}
- if (isExpanded() && !mQsController.getExpanded()) {
+ if (isExpanded() && mBarState != KEYGUARD && !mQsController.getExpanded()) {
mShadeLog.d("Status Bar was long pressed. Expanding to QS.");
expandToQs();
} else {
- mShadeLog.d("Status Bar was long pressed. Expanding to Notifications.");
- expandToNotifications();
+ if (mBarState == KEYGUARD) {
+ mShadeLog.d("Lockscreen Status Bar was long pressed. Expanding to Notifications.");
+ mLockscreenShadeTransitionController.goToLockedShade(
+ /* expandedView= */null, /* needsQSAnimation= */false);
+ } else {
+ mShadeLog.d("Status Bar was long pressed. Expanding to Notifications.");
+ expandToNotifications();
+ }
}
}
@@ -5091,13 +5097,6 @@
}
boolean handled = mHeadsUpTouchHelper.onTouchEvent(event);
- if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && mQsController.handleTouch(
- event, isFullyCollapsed(), isShadeOrQsHeightAnimationRunning())) {
- if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
- mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
- }
- return true;
- }
// This touch session has already resulted in shade expansion. Ignore everything else.
if (ShadeExpandsOnStatusBarLongPress.isEnabled()
&& event.getActionMasked() != MotionEvent.ACTION_DOWN
@@ -5105,6 +5104,13 @@
mShadeLog.d("Touch has same down time as Status Bar long press. Ignoring.");
return false;
}
+ if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && mQsController.handleTouch(
+ event, isFullyCollapsed(), isShadeOrQsHeightAnimationRunning())) {
+ if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
+ mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
+ }
+ return true;
+ }
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
handled = true;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 7a18d7c..207439e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -28,6 +28,7 @@
import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.lifecycle.lifecycleScope
import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.customization.R as customR
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.fragments.FragmentService
@@ -314,7 +315,7 @@
private fun setKeyguardStatusViewConstraints(constraintSet: ConstraintSet) {
val statusViewMarginHorizontal =
- resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+ resources.getDimensionPixelSize(customR.dimen.status_view_margin_horizontal)
constraintSet.apply {
setMargin(R.id.keyguard_status_view, START, statusViewMarginHorizontal)
setMargin(R.id.keyguard_status_view, END, statusViewMarginHorizontal)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt
rename to packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
index 6fb3ca5..ae36e81 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
@@ -25,7 +25,7 @@
/** Accepts touch events, detects long press, and calls ShadeViewController#onStatusBarLongPress. */
@SysUISingleton
-class LongPressGestureDetector
+class StatusBarLongPressGestureDetector
@Inject
constructor(context: Context, val shadeViewController: ShadeViewController) {
val gestureDetector =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 33f0c64..6bec86a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -177,8 +177,6 @@
private float[] mMatrix;
private ColorMatrixColorFilter mMatrixColorFilter;
private Runnable mLayoutRunnable;
- private boolean mDismissed;
- private Runnable mOnDismissListener;
private boolean mIncreasedSize;
private boolean mShowsConversation;
private float mDozeAmount;
@@ -956,21 +954,6 @@
mLayoutRunnable = runnable;
}
- public void setDismissed() {
- mDismissed = true;
- if (mOnDismissListener != null) {
- mOnDismissListener.run();
- }
- }
-
- public boolean isDismissed() {
- return mDismissed;
- }
-
- public void setOnDismissListener(Runnable onDismissListener) {
- mOnDismissListener = onDismissListener;
- }
-
@Override
public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
int areaTint = getTint(areas, this, tint);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 08d177f..d1de6be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1543,7 +1543,6 @@
setDragController(null);
mGroupParentWhenDismissed = mNotificationParent;
mChildAfterViewWhenDismissed = null;
- mEntry.getIcons().getStatusBarIcon().setDismissed();
if (isChildInGroup()) {
List<ExpandableNotificationRow> notificationChildren =
mNotificationParent.getAttachedChildren();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 9166e7e..786d7d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -573,8 +573,29 @@
return;
}
- mUiEventLogger.log(
- NotificationCompactHeadsUpEvent.NOTIFICATION_COMPACT_HUN_SHOWN);
+ final StatusBarNotification containingRowSbn = getContainingRowSbn();
+ if (containingRowSbn == null) {
+ return;
+ }
+
+ mUiEventLogger.logWithInstanceId(
+ NotificationCompactHeadsUpEvent.NOTIFICATION_COMPACT_HUN_SHOWN,
+ containingRowSbn.getUid(),
+ containingRowSbn.getPackageName(),
+ containingRowSbn.getInstanceId());
+ }
+
+ @Nullable
+ private StatusBarNotification getContainingRowSbn() {
+ if (mContainingNotification == null) {
+ return null;
+ }
+ final NotificationEntry entry = mContainingNotification.getEntry();
+ if (entry == null) {
+ return null;
+ }
+
+ return entry.getSbn();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index db29493..80c8e8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -171,12 +171,14 @@
import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shade.ShadeExpansionListener;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shade.ShadeLogger;
import com.android.systemui.shade.ShadeSurface;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.statusbar.phone.BarTransitions;
import com.android.systemui.statusbar.AutoHideUiElement;
@@ -366,6 +368,7 @@
private PhoneStatusBarViewController mPhoneStatusBarViewController;
private PhoneStatusBarTransitions mStatusBarTransitions;
+ private final Provider<StatusBarLongPressGestureDetector> mStatusBarLongPressGestureDetector;
private final AuthRippleController mAuthRippleController;
@WindowVisibleState private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
private final NotificationShadeWindowController mNotificationShadeWindowController;
@@ -671,6 +674,7 @@
ShadeController shadeController,
WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ Provider<StatusBarLongPressGestureDetector> statusBarLongPressGestureDetector,
ViewMediatorCallback viewMediatorCallback,
InitController initController,
@Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
@@ -778,6 +782,7 @@
mShadeController = shadeController;
mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector;
mKeyguardViewMediatorCallback = viewMediatorCallback;
mInitController = initController;
mPluginDependencyProvider = pluginDependencyProvider;
@@ -1527,6 +1532,11 @@
// to touch outside the customizer to close it, such as on the status or nav bar.
mShadeController.onStatusBarTouch(event);
}
+ if (ShadeExpandsOnStatusBarLongPress.isEnabled()
+ && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+ mStatusBarLongPressGestureDetector.get().handleTouch(event);
+ }
+
return getNotificationShadeWindowView().onTouchEvent(event);
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 91c43dd..176dd8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -39,8 +39,8 @@
import com.android.systemui.Flags;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.res.R;
-import com.android.systemui.shade.LongPressGestureDetector;
import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
@@ -69,7 +69,7 @@
private InsetsFetcher mInsetsFetcher;
private int mDensity;
private float mFontScale;
- private LongPressGestureDetector mLongPressGestureDetector;
+ private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
/**
* Draw this many pixels into the left/right side of the cutout to optimally use the space
@@ -81,9 +81,10 @@
mStatusBarWindowControllerStore = Dependency.get(StatusBarWindowControllerStore.class);
}
- void setLongPressGestureDetector(LongPressGestureDetector longPressGestureDetector) {
+ void setLongPressGestureDetector(
+ StatusBarLongPressGestureDetector statusBarLongPressGestureDetector) {
if (ShadeExpandsOnStatusBarLongPress.isEnabled()) {
- mLongPressGestureDetector = longPressGestureDetector;
+ mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector;
}
}
@@ -207,8 +208,9 @@
@Override
public boolean onTouchEvent(MotionEvent event) {
- if (ShadeExpandsOnStatusBarLongPress.isEnabled() && mLongPressGestureDetector != null) {
- mLongPressGestureDetector.handleTouch(event);
+ if (ShadeExpandsOnStatusBarLongPress.isEnabled()
+ && mStatusBarLongPressGestureDetector != null) {
+ mStatusBarLongPressGestureDetector.handleTouch(event);
}
if (mTouchEventHandler == null) {
Log.w(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index a94db49..16e023c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -34,11 +34,11 @@
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.shade.LongPressGestureDetector
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress
import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.StatusBarLongPressGestureDetector
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
@@ -69,7 +69,7 @@
private val shadeController: ShadeController,
private val shadeViewController: ShadeViewController,
private val panelExpansionInteractor: PanelExpansionInteractor,
- private val longPressGestureDetector: Provider<LongPressGestureDetector>,
+ private val statusBarLongPressGestureDetector: Provider<StatusBarLongPressGestureDetector>,
private val windowRootView: Provider<WindowRootView>,
private val shadeLogger: ShadeLogger,
private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
@@ -119,7 +119,7 @@
addCursorSupportToIconContainers()
if (ShadeExpandsOnStatusBarLongPress.isEnabled) {
- mView.setLongPressGestureDetector(longPressGestureDetector.get())
+ mView.setLongPressGestureDetector(statusBarLongPressGestureDetector.get())
}
progressProvider?.setReadyToHandleTransition(true)
@@ -336,7 +336,7 @@
private val shadeController: ShadeController,
private val shadeViewController: ShadeViewController,
private val panelExpansionInteractor: PanelExpansionInteractor,
- private val longPressGestureDetector: Provider<LongPressGestureDetector>,
+ private val statusBarLongPressGestureDetector: Provider<StatusBarLongPressGestureDetector>,
private val windowRootView: Provider<WindowRootView>,
private val shadeLogger: ShadeLogger,
private val viewUtil: ViewUtil,
@@ -361,7 +361,7 @@
shadeController,
shadeViewController,
panelExpansionInteractor,
- longPressGestureDetector,
+ statusBarLongPressGestureDetector,
windowRootView,
shadeLogger,
statusBarMoveFromCenterAnimationController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
index 13ac321..f3d5139 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -113,34 +113,38 @@
}
private val showIcon =
- canShowIcon
- .flatMapLatest { canShow ->
- if (!canShow) {
- flowOf(false)
- } else {
- combine(
- shouldShowIconForOosAfterHysteresis,
- interactor.connectionState,
- interactor.isWifiActive,
- airplaneModeRepository.isAirplaneMode,
- ) { showForOos, connectionState, isWifiActive, isAirplaneMode ->
- if (isWifiActive || isAirplaneMode) {
- false
- } else {
- showForOos ||
- connectionState == SatelliteConnectionState.On ||
- connectionState == SatelliteConnectionState.Connected
+ if (interactor.isOpportunisticSatelliteIconEnabled) {
+ canShowIcon
+ .flatMapLatest { canShow ->
+ if (!canShow) {
+ flowOf(false)
+ } else {
+ combine(
+ shouldShowIconForOosAfterHysteresis,
+ interactor.connectionState,
+ interactor.isWifiActive,
+ airplaneModeRepository.isAirplaneMode,
+ ) { showForOos, connectionState, isWifiActive, isAirplaneMode ->
+ if (isWifiActive || isAirplaneMode) {
+ false
+ } else {
+ showForOos ||
+ connectionState == SatelliteConnectionState.On ||
+ connectionState == SatelliteConnectionState.Connected
+ }
}
}
}
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLog,
+ columnPrefix = "vm",
+ columnName = COL_VISIBLE,
+ initialValue = false,
+ )
+ } else {
+ flowOf(false)
}
- .distinctUntilChanged()
- .logDiffsForTable(
- tableLog,
- columnPrefix = "vm",
- columnName = COL_VISIBLE,
- initialValue = false,
- )
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val icon: StateFlow<Icon?> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index a1b56d6..a72c83e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -92,8 +92,7 @@
private var wifiPickerTracker: WifiPickerTracker? = null
- @VisibleForTesting
- val selectedUserContext: Flow<Context> =
+ private val selectedUserContext: Flow<Context> =
userRepository.selectedUserInfo.map {
applicationContext.createContextAsUser(UserHandle.of(it.id), /* flags= */ 0)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
index 584cd3b..1e043ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
@@ -36,6 +36,9 @@
/** Adds the status bar view to the window manager. */
fun attach()
+ /** Called when work should stop and resources should be released. */
+ fun stop()
+
/** Adds the given view to the status bar window view. */
fun addViewToWindow(view: View, layoutParams: ViewGroup.LayoutParams)
@@ -78,7 +81,7 @@
*/
fun setOngoingProcessRequiresStatusBarVisible(visible: Boolean)
- interface Factory {
+ fun interface Factory {
fun create(
context: Context,
viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
index 6953bbf..811a2ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
@@ -51,10 +51,12 @@
import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.animation.DelegateTransitionAnimatorController;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays;
+import com.android.systemui.statusbar.core.StatusBarRootModernization;
import com.android.systemui.statusbar.data.repository.StatusBarConfigurationController;
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
import com.android.systemui.statusbar.window.StatusBarWindowModule.InternalWindowViewInflater;
@@ -66,6 +68,7 @@
import dagger.assisted.AssistedInject;
import java.util.Optional;
+import java.util.concurrent.Executor;
/**
* Encapsulates all logic for the status bar window state management.
@@ -79,6 +82,7 @@
private final StatusBarConfigurationController mStatusBarConfigurationController;
private final IWindowManager mIWindowManager;
private final StatusBarContentInsetsProvider mContentInsetsProvider;
+ private final Executor mMainExecutor;
private int mBarHeight = -1;
private final State mCurrentState = new State();
private boolean mIsAttached;
@@ -101,12 +105,14 @@
IWindowManager iWindowManager,
@Assisted StatusBarContentInsetsProvider contentInsetsProvider,
FragmentService fragmentService,
- Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider) {
+ Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider,
+ @Main Executor mainExecutor) {
mContext = context;
mWindowManager = viewCaptureAwareWindowManager;
mStatusBarConfigurationController = statusBarConfigurationController;
mIWindowManager = iWindowManager;
mContentInsetsProvider = contentInsetsProvider;
+ mMainExecutor = mainExecutor;
mStatusBarWindowView = statusBarWindowViewInflater.inflate(context);
mFragmentService = fragmentService;
mLaunchAnimationContainer = mStatusBarWindowView.findViewById(
@@ -167,6 +173,19 @@
}
@Override
+ public void stop() {
+ StatusBarConnectedDisplays.assertInNewMode();
+
+ mWindowManager.removeView(mStatusBarWindowView);
+
+ if (StatusBarRootModernization.isEnabled()) {
+ return;
+ }
+ // Fragment transactions need to happen on the main thread.
+ mMainExecutor.execute(() -> mFragmentService.removeAndDestroy(mStatusBarWindowView));
+ }
+
+ @Override
public void addViewToWindow(@NonNull View view, @NonNull ViewGroup.LayoutParams layoutParams) {
mStatusBarWindowView.addView(view, layoutParams);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
index 051d463..7403161 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
@@ -70,6 +70,10 @@
)
}
+ override suspend fun onDisplayRemovalAction(instance: StatusBarWindowController) {
+ instance.stop()
+ }
+
override val instanceClass = StatusBarWindowController::class.java
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
index 6816d35..c4b028d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
@@ -49,7 +49,7 @@
val drawerContainer = requireViewById<View>(R.id.volume_drawer_container)
val selectedButtonView =
requireViewById<ImageButton>(R.id.volume_new_ringer_active_button)
- val volumeDialogView = requireViewById<ViewGroup>(R.id.volume_dialog)
+ val volumeDialogBackgroundView = requireViewById<View>(R.id.volume_dialog_background)
repeatWhenAttached {
viewModel(
traceName = "VolumeDialogRingerViewBinder",
@@ -71,19 +71,17 @@
is RingerDrawerState.Initial -> {
drawerContainer.visibility = View.GONE
selectedButtonView.visibility = View.VISIBLE
- volumeDialogView.setBackgroundResource(
+ volumeDialogBackgroundView.setBackgroundResource(
R.drawable.volume_dialog_background
)
}
-
is RingerDrawerState.Closed -> {
drawerContainer.visibility = View.GONE
selectedButtonView.visibility = View.VISIBLE
- volumeDialogView.setBackgroundResource(
+ volumeDialogBackgroundView.setBackgroundResource(
R.drawable.volume_dialog_background
)
}
-
is RingerDrawerState.Open -> {
drawerContainer.visibility = View.VISIBLE
selectedButtonView.visibility = View.GONE
@@ -91,17 +89,16 @@
uiModel.currentButtonIndex !=
uiModel.availableButtons.size - 1
) {
- volumeDialogView.setBackgroundResource(
+ volumeDialogBackgroundView.setBackgroundResource(
R.drawable.volume_dialog_background_small_radius
)
}
}
}
}
-
is RingerViewModelState.Unavailable -> {
drawerAndRingerContainer.visibility = View.GONE
- volumeDialogView.setBackgroundResource(
+ volumeDialogBackgroundView.setBackgroundResource(
R.drawable.volume_dialog_background
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
index a17c1e5..9078f82 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
@@ -38,9 +38,10 @@
fun bind(view: View) {
with(view) {
- val volumeDialog: View = requireViewById(R.id.volume_dialog)
val floatingSlidersContainer: ViewGroup =
requireViewById(R.id.volume_dialog_floating_sliders_container)
+ val mainSliderContainer: View =
+ requireViewById(R.id.volume_dialog_main_slider_container)
repeatWhenAttached {
viewModel(
traceName = "VolumeDialogSlidersViewBinder",
@@ -49,7 +50,7 @@
) { viewModel ->
viewModel.sliders
.onEach { uiModel ->
- uiModel.sliderComponent.sliderViewBinder().bind(volumeDialog)
+ uiModel.sliderComponent.sliderViewBinder().bind(mainSliderContainer)
val floatingSliderViewBinders = uiModel.floatingSliderComponent
floatingSlidersContainer.ensureChildCount(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
index d9a945c..f6c1743 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
@@ -19,13 +19,11 @@
import android.app.Dialog
import android.graphics.Rect
import android.graphics.Region
-import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.view.ViewTreeObserver.InternalInsetsInfo
-import android.widget.FrameLayout
-import androidx.annotation.GravityInt
+import androidx.constraintlayout.motion.widget.MotionLayout
import com.android.internal.view.RotationPolicy
import com.android.systemui.lifecycle.WindowLifecycleState
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -41,7 +39,6 @@
import com.android.systemui.volume.dialog.ui.VolumeDialogResources
import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory
import com.android.systemui.volume.dialog.ui.utils.suspendAnimate
-import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogGravityViewModel
import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogViewModel
import com.android.systemui.volume.dialog.utils.VolumeTracer
import javax.inject.Inject
@@ -53,6 +50,7 @@
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
@@ -63,7 +61,6 @@
@Inject
constructor(
private val volumeResources: VolumeDialogResources,
- private val gravityViewModel: VolumeDialogGravityViewModel,
private val dialogViewModelFactory: VolumeDialogViewModel.Factory,
private val jankListenerFactory: JankListenerFactory,
private val tracer: VolumeTracer,
@@ -74,21 +71,23 @@
fun bind(dialog: Dialog) {
// Root view of the Volume Dialog.
- val root: ViewGroup = dialog.requireViewById(R.id.volume_dialog_root)
- // Volume Dialog container view that contains the dialog itself without the floating sliders
- val container: View = root.requireViewById(R.id.volume_dialog_container)
- container.alpha = 0f
- container.repeatWhenAttached {
+ val root: MotionLayout = dialog.requireViewById(R.id.volume_dialog_root)
+ root.alpha = 0f
+ root.repeatWhenAttached {
root.viewModel(
traceName = "VolumeDialogViewBinder",
minWindowLifecycleState = WindowLifecycleState.ATTACHED,
factory = { dialogViewModelFactory.create() },
) { viewModel ->
- animateVisibility(container, dialog, viewModel.dialogVisibilityModel)
+ animateVisibility(root, dialog, viewModel.dialogVisibilityModel)
viewModel.dialogTitle.onEach { dialog.window?.setTitle(it) }.launchIn(this)
- gravityViewModel.dialogGravity
- .onEach { container.setLayoutGravity(it) }
+ viewModel.motionState
+ .scan(0) { acc, motionState ->
+ // don't animate the initial state
+ root.transitionToState(motionState, animate = acc != 0)
+ acc + 1
+ }
.launchIn(this)
launch { root.viewTreeObserver.computeInternalInsetsListener(root) }
@@ -130,15 +129,13 @@
.launchIn(this)
}
- private suspend fun calculateTranslationX(view: View): Float? {
+ private fun calculateTranslationX(view: View): Float? {
return if (view.display.rotation == RotationPolicy.NATURAL_ROTATION) {
- val dialogGravity = gravityViewModel.dialogGravity.first()
- val isGravityLeft = (dialogGravity and Gravity.LEFT) == Gravity.LEFT
- if (isGravityLeft) {
+ if (view.isLayoutRtl) {
-1
} else {
1
- } * view.width / 2.0f
+ } * view.width / 2f
} else {
null
}
@@ -211,10 +208,11 @@
getBoundsInWindow(boundsRect, false)
}
- private fun View.setLayoutGravity(@GravityInt newGravity: Int) {
- val frameLayoutParams =
- layoutParams as? FrameLayout.LayoutParams
- ?: error("View must be a child of a FrameLayout")
- layoutParams = frameLayoutParams.apply { gravity = newGravity }
+ private fun MotionLayout.transitionToState(newState: Int, animate: Boolean) {
+ if (animate) {
+ transitionToState(newState)
+ } else {
+ jumpToState(newState)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt
deleted file mode 100644
index 112afb1..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.volume.dialog.ui.viewmodel
-
-import android.content.Context
-import android.content.res.Configuration
-import android.view.Gravity
-import androidx.annotation.GravityInt
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.UiBackground
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.DevicePostureController
-import com.android.systemui.statusbar.policy.devicePosture
-import com.android.systemui.statusbar.policy.onConfigChanged
-import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
-import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
-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.combine
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.withContext
-
-/** Exposes dialog [GravityInt] for use in the UI layer. */
-@VolumeDialogScope
-class VolumeDialogGravityViewModel
-@Inject
-constructor(
- @Application private val context: Context,
- @VolumeDialog private val coroutineScope: CoroutineScope,
- @UiBackground private val uiBackgroundCoroutineContext: CoroutineContext,
- configurationController: ConfigurationController,
- private val devicePostureController: DevicePostureController,
-) {
-
- @GravityInt private var originalGravity: Int = context.getAbsoluteGravity()
-
- val dialogGravity: Flow<Int> =
- combine(
- devicePostureController.devicePosture(),
- configurationController.onConfigChanged.onEach { onConfigurationChanged() },
- ) { devicePosture, configuration ->
- context.calculateGravity(devicePosture, configuration)
- }
- .stateIn(
- scope = coroutineScope,
- started = SharingStarted.Eagerly,
- context.calculateGravity(),
- )
-
- private suspend fun onConfigurationChanged() {
- withContext(uiBackgroundCoroutineContext) { originalGravity = context.getAbsoluteGravity() }
- }
-
- @GravityInt
- private fun Context.calculateGravity(
- devicePosture: Int = devicePostureController.devicePosture,
- config: Configuration = resources.configuration,
- ): Int {
- val isLandscape = config.orientation == Configuration.ORIENTATION_LANDSCAPE
- val isHalfOpen = devicePosture == DevicePostureController.DEVICE_POSTURE_HALF_OPENED
- val gravity =
- if (isLandscape && isHalfOpen) {
- originalGravity or Gravity.TOP
- } else {
- originalGravity
- }
- return getAbsoluteGravity(gravity)
- }
-}
-
-@GravityInt
-private fun Context.getAbsoluteGravity(
- gravity: Int = resources.getInteger(R.integer.volume_dialog_gravity)
-): Int = with(resources) { Gravity.getAbsoluteGravity(gravity, configuration.layoutDirection) }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
index 869a6a2..0352799 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
@@ -17,7 +17,12 @@
package com.android.systemui.volume.dialog.ui.viewmodel
import android.content.Context
+import android.content.res.Configuration
import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.statusbar.policy.devicePosture
+import com.android.systemui.statusbar.policy.onConfigChanged
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStateModel
@@ -32,6 +37,7 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
/** Provides a state for the Volume Dialog. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -42,8 +48,23 @@
dialogVisibilityInteractor: VolumeDialogVisibilityInteractor,
volumeDialogSlidersInteractor: VolumeDialogSlidersInteractor,
volumeDialogStateInteractor: VolumeDialogStateInteractor,
+ devicePostureController: DevicePostureController,
+ configurationController: ConfigurationController,
) {
+ val motionState: Flow<Int> =
+ combine(
+ devicePostureController.devicePosture(),
+ configurationController.onConfigChanged.onStart {
+ emit(context.resources.configuration)
+ },
+ ) { devicePosture, configuration ->
+ if (shouldOffsetVolumeDialog(devicePosture, configuration)) {
+ R.id.volume_dialog_half_folded_constraint_set
+ } else {
+ R.id.volume_dialog_constraint_set
+ }
+ }
val dialogVisibilityModel: Flow<VolumeDialogVisibilityModel> =
dialogVisibilityInteractor.dialogVisibility
val dialogTitle: Flow<String> =
@@ -57,6 +78,13 @@
}
.filterNotNull()
+ /** @return true when the foldable device screen curve is in the way of the volume dialog */
+ private fun shouldOffsetVolumeDialog(devicePosture: Int, config: Configuration): Boolean {
+ val isLandscape = config.orientation == Configuration.ORIENTATION_LANDSCAPE
+ val isHalfOpen = devicePosture == DevicePostureController.DEVICE_POSTURE_HALF_OPENED
+ return isLandscape && isHalfOpen
+ }
+
@AssistedFactory
interface Factory {
fun create(): VolumeDialogViewModel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index c9ada7e..b142fc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -155,6 +155,7 @@
import com.android.systemui.shade.ShadeControllerImpl;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shade.ShadeLogger;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyboardShortcutListSearch;
@@ -174,7 +175,6 @@
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays;
import com.android.systemui.statusbar.core.StatusBarInitializerImpl;
-import com.android.systemui.statusbar.core.StatusBarOrchestrator;
import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
@@ -372,7 +372,7 @@
@Mock private EmergencyGestureIntentFactory mEmergencyGestureIntentFactory;
@Mock private NotificationSettingsInteractor mNotificationSettingsInteractor;
@Mock private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
- @Mock private StatusBarOrchestrator mStatusBarOrchestrator;
+ @Mock private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
private ShadeController mShadeController;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeGlobalSettings mFakeGlobalSettings = new FakeGlobalSettings();
@@ -607,6 +607,7 @@
mShadeController,
mWindowRootViewVisibilityInteractor,
mStatusBarKeyguardViewManager,
+ () -> mStatusBarLongPressGestureDetector,
mViewMediatorCallback,
mInitController,
new Handler(TestableLooper.get(this).getLooper()),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 638f195..69efa87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -40,14 +40,13 @@
import com.android.systemui.plugins.fakeDarkIconDispatcher
import com.android.systemui.res.R
import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.shade.LongPressGestureDetector
import com.android.systemui.shade.ShadeControllerImpl
import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.StatusBarLongPressGestureDetector
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.data.repository.fakeStatusBarContentInsetsProviderStore
-import com.android.systemui.statusbar.data.repository.statusBarContentInsetsProviderStore
import com.android.systemui.statusbar.policy.Clock
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.window.StatusBarWindowStateController
@@ -98,7 +97,7 @@
@Mock private lateinit var windowRootView: Provider<WindowRootView>
@Mock private lateinit var shadeLogger: ShadeLogger
@Mock private lateinit var viewUtil: ViewUtil
- @Mock private lateinit var longPressGestureDetector: LongPressGestureDetector
+ @Mock private lateinit var mStatusBarLongPressGestureDetector: StatusBarLongPressGestureDetector
private lateinit var statusBarWindowStateController: StatusBarWindowStateController
private lateinit var view: PhoneStatusBarView
@@ -395,7 +394,7 @@
shadeControllerImpl,
shadeViewController,
panelExpansionInteractor,
- { longPressGestureDetector },
+ { mStatusBarLongPressGestureDetector },
windowRootView,
shadeLogger,
viewUtil,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt
index 528c9d9..a110a49 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt
@@ -27,6 +27,9 @@
var isAttached = false
private set
+ var isStopped = false
+ private set
+
override val statusBarHeight: Int = 0
override fun refreshStatusBarHeight() {}
@@ -35,6 +38,10 @@
isAttached = true
}
+ override fun stop() {
+ isStopped = true
+ }
+
override fun addViewToWindow(view: View, layoutParams: ViewGroup.LayoutParams) {}
override val backgroundView: View
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
index 173e909..23f2b42 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
@@ -18,7 +18,8 @@
import android.content.testableContext
import android.view.windowManagerService
-import com.android.app.viewcapture.viewCaptureAwareWindowManager
+import com.android.app.viewcapture.realCaptureAwareWindowManager
+import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.fragments.fragmentService
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.statusbar.phone.statusBarContentInsetsProvider
@@ -32,12 +33,13 @@
StatusBarWindowControllerImpl(
testableContext,
statusBarWindowViewInflater,
- viewCaptureAwareWindowManager,
+ realCaptureAwareWindowManager,
statusBarConfigurationController,
windowManagerService,
statusBarContentInsetsProvider,
fragmentService,
Optional.empty(),
+ fakeExecutor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStoreKosmos.kt
new file mode 100644
index 0000000..4941ceb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStoreKosmos.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.statusbar.window
+
+import android.view.WindowManager
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.app.viewcapture.realCaptureAwareWindowManager
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.displayWindowPropertiesRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.data.repository.statusBarConfigurationControllerStore
+import com.android.systemui.statusbar.data.repository.statusBarContentInsetsProviderStore
+import org.mockito.kotlin.mock
+
+val Kosmos.multiDisplayStatusBarWindowControllerStore by
+ Kosmos.Fixture {
+ MultiDisplayStatusBarWindowControllerStore(
+ backgroundApplicationScope = applicationCoroutineScope,
+ controllerFactory = { _, _, _, _ -> mock() },
+ displayWindowPropertiesRepository = displayWindowPropertiesRepository,
+ viewCaptureAwareWindowManagerFactory =
+ object : ViewCaptureAwareWindowManager.Factory {
+ override fun create(
+ windowManager: WindowManager
+ ): ViewCaptureAwareWindowManager {
+ return realCaptureAwareWindowManager
+ }
+ },
+ statusBarConfigurationControllerStore = statusBarConfigurationControllerStore,
+ statusBarContentInsetsProviderStore = statusBarContentInsetsProviderStore,
+ displayRepository = displayRepository,
+ )
+ }
diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig
index bd46deb..b3fe5f2 100644
--- a/services/autofill/features.aconfig
+++ b/services/autofill/features.aconfig
@@ -2,6 +2,13 @@
container: "system"
flag {
+ name: "autofill_w_metrics"
+ namespace: "autofill"
+ description: "Guards against new metrics definitions introduced in W"
+ bug: "342676602"
+}
+
+flag {
name: "autofill_credman_integration"
namespace: "autofill"
description: "Guards Autofill Framework against Autofill-Credman integration"
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index d53f949..fcb7934 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -60,3 +60,12 @@
bug: "331749778"
is_fixed_read_only: true
}
+
+flag {
+ name: "enable_restricted_mode_changes"
+ namespace: "onboarding"
+ description: "Enables the new framework behavior of not putting apps in restricted mode for "
+ "B&R operations in certain cases."
+ bug: "376661510"
+ is_fixed_read_only: true
+}
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 466d477..5de2fb3 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -43,6 +43,7 @@
import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
import android.app.AppGlobals;
+import android.app.ApplicationThreadConstants;
import android.app.IActivityManager;
import android.app.IBackupAgent;
import android.app.PendingIntent;
@@ -59,6 +60,9 @@
import android.app.backup.IFullBackupRestoreObserver;
import android.app.backup.IRestoreSession;
import android.app.backup.ISelectBackupTransportCallback;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -298,6 +302,15 @@
private static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED";
private static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName";
+ /**
+ * Enables the OS making a decision on whether backup restricted mode should be used for apps
+ * that haven't explicitly opted in or out. See
+ * {@link PackageManager#PROPERTY_USE_RESTRICTED_BACKUP_MODE} for details.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.BAKLAVA)
+ public static final long OS_DECIDES_BACKUP_RESTRICTED_MODE = 376661510;
+
// Time delay for initialization operations that can be delayed so as not to consume too much
// CPU on bring-up and increase time-to-UI.
private static final long INITIALIZATION_DELAY_MILLIS = 3000;
@@ -352,6 +365,9 @@
// Backups that we haven't started yet. Keys are package names.
private final HashMap<String, BackupRequest> mPendingBackups = new HashMap<>();
+ private final ArraySet<String> mRestoreNoRestrictedModePackages = new ArraySet<>();
+ private final ArraySet<String> mBackupNoRestrictedModePackages = new ArraySet<>();
+
// locking around the pending-backup management
private final Object mQueueLock = new Object();
@@ -523,7 +539,8 @@
@VisibleForTesting
UserBackupManagerService(Context context, PackageManager packageManager,
LifecycleOperationStorage operationStorage, TransportManager transportManager,
- BackupHandler backupHandler, BackupManagerConstants backupManagerConstants) {
+ BackupHandler backupHandler, BackupManagerConstants backupManagerConstants,
+ IActivityManager activityManager, ActivityManagerInternal activityManagerInternal) {
mContext = context;
mUserId = 0;
@@ -534,6 +551,8 @@
mFullBackupQueue = new ArrayList<>();
mBackupHandler = backupHandler;
mConstants = backupManagerConstants;
+ mActivityManager = activityManager;
+ mActivityManagerInternal = activityManagerInternal;
mBaseStateDir = null;
mDataDir = null;
@@ -543,13 +562,11 @@
mRunInitReceiver = null;
mRunInitIntent = null;
mAgentTimeoutParameters = null;
- mActivityManagerInternal = null;
mAlarmManager = null;
mWakelock = null;
mBackupPreferences = null;
mBackupPasswordManager = null;
mPackageManagerBinder = null;
- mActivityManager = null;
mBackupManagerBinder = null;
mScheduledBackupEligibility = null;
}
@@ -1651,9 +1668,11 @@
synchronized (mAgentConnectLock) {
mConnecting = true;
mConnectedAgent = null;
+ boolean useRestrictedMode = shouldUseRestrictedBackupModeForPackage(mode,
+ app.packageName);
try {
if (mActivityManager.bindBackupAgent(app.packageName, mode, mUserId,
- backupDestination)) {
+ backupDestination, useRestrictedMode)) {
Slog.d(TAG, addUserIdToLogMessage(mUserId, "awaiting agent for " + app));
// success; wait for the agent to arrive
@@ -3103,6 +3122,91 @@
}
}
+ /**
+ * Marks the given set of packages as packages that should not be put into restricted mode if
+ * they are started for the given {@link BackupAnnotations.OperationType}.
+ */
+ public void setNoRestrictedModePackages(Set<String> packageNames,
+ @BackupAnnotations.OperationType int opType) {
+ if (opType == BackupAnnotations.OperationType.BACKUP) {
+ mBackupNoRestrictedModePackages.clear();
+ mBackupNoRestrictedModePackages.addAll(packageNames);
+ } else if (opType == BackupAnnotations.OperationType.RESTORE) {
+ mRestoreNoRestrictedModePackages.clear();
+ mRestoreNoRestrictedModePackages.addAll(packageNames);
+ } else {
+ throw new IllegalArgumentException("opType must be BACKUP or RESTORE");
+ }
+ }
+
+ /**
+ * Clears the list of packages that should not be put into restricted mode for either backup or
+ * restore.
+ */
+ public void clearNoRestrictedModePackages() {
+ mBackupNoRestrictedModePackages.clear();
+ mRestoreNoRestrictedModePackages.clear();
+ }
+
+ /**
+ * If the app has specified {@link PackageManager#PROPERTY_USE_RESTRICTED_BACKUP_MODE}, then
+ * its value is returned. If it hasn't and it targets an SDK below
+ * {@link Build.VERSION_CODES#BAKLAVA} then returns true. If it targets a newer SDK, then
+ * returns the decision made by the {@link android.app.backup.BackupTransport}.
+ *
+ * <p>When this method is called, we should have already asked the transport and cached its
+ * response in {@link #mBackupNoRestrictedModePackages} or
+ * {@link #mRestoreNoRestrictedModePackages} so this method will immediately return without
+ * any IPC to the transport.
+ */
+ private boolean shouldUseRestrictedBackupModeForPackage(
+ @BackupAnnotations.OperationType int mode, String packageName) {
+ if (!Flags.enableRestrictedModeChanges()) {
+ return true;
+ }
+
+ // Key/Value apps are never put in restricted mode.
+ if (mode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL
+ || mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE) {
+ return false;
+ }
+
+ try {
+ PackageManager.Property property = mPackageManager.getPropertyAsUser(
+ PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE,
+ packageName, /* className= */ null,
+ mUserId);
+ if (property.isBoolean()) {
+ // If the package has explicitly specified, we won't ask the transport.
+ return property.getBoolean();
+ } else {
+ Slog.w(TAG, PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE
+ + "must be a boolean.");
+ }
+ } catch (NameNotFoundException e) {
+ // This is expected when the package has not defined the property in its manifest.
+ }
+
+ // The package has not specified the property. The behavior depends on the package's
+ // targetSdk.
+ // <36 gets the old behavior of always using restricted mode.
+ if (!CompatChanges.isChangeEnabled(OS_DECIDES_BACKUP_RESTRICTED_MODE, packageName,
+ UserHandle.of(mUserId))) {
+ return true;
+ }
+
+ // Apps targeting >=36 get the behavior decided by the transport.
+ // By this point, we should have asked the transport and cached its decision.
+ if ((mode == ApplicationThreadConstants.BACKUP_MODE_FULL
+ && mBackupNoRestrictedModePackages.contains(packageName))
+ || (mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL
+ && mRestoreNoRestrictedModePackages.contains(packageName))) {
+ Slog.d(TAG, "Transport requested no restricted mode for: " + packageName);
+ return false;
+ }
+ return true;
+ }
+
private boolean startConfirmationUi(int token, String action) {
try {
Intent confIntent = new Intent(action);
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index cca166b..be9cdc8 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -16,6 +16,8 @@
package com.android.server.backup.fullbackup;
+import static android.app.backup.BackupAnnotations.OperationType.BACKUP;
+
import static com.android.server.backup.BackupManagerService.DEBUG;
import static com.android.server.backup.BackupManagerService.DEBUG_SCHEDULING;
import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
@@ -34,6 +36,7 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.util.ArraySet;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
@@ -388,6 +391,10 @@
}
}
+ // We ask the transport which packages should not be put in restricted mode and cache
+ // the result in UBMS to be used later when the apps are started for backup.
+ setNoRestrictedModePackages(transport, mPackages);
+
// Set up to send data to the transport
final int N = mPackages.size();
int chunkSizeInBytes = 8 * 1024; // 8KB
@@ -694,6 +701,9 @@
mUserBackupManagerService.scheduleNextFullBackupJob(backoff);
}
+ // Clear this to avoid using the memory until reboot.
+ mUserBackupManagerService.clearNoRestrictedModePackages();
+
Slog.i(TAG, "Full data backup pass finished.");
mUserBackupManagerService.getWakelock().release();
}
@@ -722,6 +732,21 @@
}
}
+ private void setNoRestrictedModePackages(BackupTransportClient transport,
+ List<PackageInfo> packages) {
+ try {
+ Set<String> packageNames = new ArraySet<>();
+ for (int i = 0; i < packages.size(); i++) {
+ packageNames.add(packages.get(i).packageName);
+ }
+ packageNames = transport.getPackagesThatShouldNotUseRestrictedMode(packageNames,
+ BACKUP);
+ mUserBackupManagerService.setNoRestrictedModePackages(packageNames, BACKUP);
+ } catch (RemoteException e) {
+ Slog.i(TAG, "Failed to retrieve no restricted mode packages from transport");
+ }
+ }
+
// Run the backup and pipe it back to the given socket -- expects to run on
// a standalone thread. The runner owns this half of the pipe, and closes
// it to indicate EOD to the other end.
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index e536876..5ee51a5 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -53,6 +53,7 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.ArraySet;
import android.util.EventLog;
import android.util.Slog;
@@ -482,6 +483,10 @@
return;
}
+ // We ask the transport which packages should not be put in restricted mode and cache
+ // the result in UBMS to be used later when the apps are started for restore.
+ setNoRestrictedModePackages(transport, packages);
+
RestoreDescription desc = transport.nextRestorePackage();
if (desc == null) {
Slog.e(TAG, "No restore metadata available; halting");
@@ -1358,6 +1363,9 @@
// Clear any ongoing session timeout.
backupManagerService.getBackupHandler().removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
+ // Clear this to avoid using the memory until reboot.
+ backupManagerService.clearNoRestrictedModePackages();
+
// If we have a PM token, we must under all circumstances be sure to
// handshake when we've finished.
if (mPmToken > 0) {
@@ -1819,4 +1827,20 @@
return packageInfo;
}
+
+ @VisibleForTesting
+ void setNoRestrictedModePackages(BackupTransportClient transport,
+ PackageInfo[] packages) {
+ try {
+ Set<String> packageNames = new ArraySet<>();
+ for (int i = 0; i < packages.length; i++) {
+ packageNames.add(packages[i].packageName);
+ }
+ packageNames = transport.getPackagesThatShouldNotUseRestrictedMode(packageNames,
+ RESTORE);
+ backupManagerService.setNoRestrictedModePackages(packageNames, RESTORE);
+ } catch (RemoteException e) {
+ Slog.i(TAG, "Failed to retrieve restricted mode packages from transport");
+ }
+ }
}
diff --git a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
index daf34152..373811f 100644
--- a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
+++ b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
@@ -17,6 +17,7 @@
package com.android.server.backup.transport;
import android.annotation.Nullable;
+import android.app.backup.BackupAnnotations;
import android.app.backup.BackupTransport;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.RestoreDescription;
@@ -26,6 +27,7 @@
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.backup.IBackupTransport;
@@ -375,6 +377,26 @@
}
/**
+ * See
+ * {@link IBackupTransport#getPackagesThatShouldNotUseRestrictedMode(List, int, AndroidFuture)}.
+ */
+ public Set<String> getPackagesThatShouldNotUseRestrictedMode(Set<String> packageNames,
+ @BackupAnnotations.OperationType
+ int operationType) throws RemoteException {
+ AndroidFuture<List<String>> resultFuture = mTransportFutures.newFuture();
+ mTransportBinder.getPackagesThatShouldNotUseRestrictedMode(List.copyOf(packageNames),
+ operationType,
+ resultFuture);
+ List<String> resultList = getFutureResult(resultFuture);
+ Set<String> set = new ArraySet<>();
+ if (resultList == null) {
+ return set;
+ }
+ set.addAll(resultList);
+ return set;
+ }
+
+ /**
* Allows the {@link TransportConnection} to notify this client
* if the underlying transport has become unusable. If that happens
* we want to cancel all active futures or callbacks.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index dfddc08..d880bce 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -478,7 +478,6 @@
import dalvik.annotation.optimization.NeverCompile;
import dalvik.system.VMRuntime;
-
import libcore.util.EmptyArray;
import java.io.File;
@@ -4493,16 +4492,11 @@
Slog.w(TAG, "Unattached app died before backup, skipping");
final int userId = app.userId;
final String packageName = app.info.packageName;
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- try {
- IBackupManager bm = IBackupManager.Stub.asInterface(
- ServiceManager.getService(Context.BACKUP_SERVICE));
- bm.agentDisconnectedForUser(userId, packageName);
- } catch (RemoteException e) {
- // Can't happen; the backup manager is local
- }
+ mHandler.post(() -> {
+ try {
+ getBackupManager().agentDisconnectedForUser(userId, packageName);
+ } catch (RemoteException e) {
+ // Can't happen; the backup manager is local
}
});
}
@@ -4673,7 +4667,8 @@
if (backupTarget != null && backupTarget.appInfo.packageName.equals(processName)) {
isRestrictedBackupMode = backupTarget.appInfo.uid >= FIRST_APPLICATION_UID
&& ((backupTarget.backupMode == BackupRecord.RESTORE_FULL)
- || (backupTarget.backupMode == BackupRecord.BACKUP_FULL));
+ || (backupTarget.backupMode == BackupRecord.BACKUP_FULL))
+ && backupTarget.useRestrictedMode;
}
final ActiveInstrumentation instr = app.getActiveInstrumentation();
@@ -13499,16 +13494,11 @@
if (backupTarget != null && pid == backupTarget.app.getPid()) {
if (DEBUG_BACKUP || DEBUG_CLEANUP) Slog.d(TAG_CLEANUP, "App "
+ backupTarget.appInfo + " died during backup");
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- try {
- IBackupManager bm = IBackupManager.Stub.asInterface(
- ServiceManager.getService(Context.BACKUP_SERVICE));
- bm.agentDisconnectedForUser(app.userId, app.info.packageName);
- } catch (RemoteException e) {
- // can't happen; backup manager is local
- }
+ mHandler.post(() -> {
+ try {
+ getBackupManager().agentDisconnectedForUser(app.userId, app.info.packageName);
+ } catch (RemoteException e) {
+ // can't happen; backup manager is local
}
});
}
@@ -14011,7 +14001,7 @@
// instantiated. The backup agent will invoke backupAgentCreated() on the
// activity manager to announce its creation.
public boolean bindBackupAgent(String packageName, int backupMode, int targetUserId,
- @BackupDestination int backupDestination) {
+ @BackupDestination int backupDestination, boolean useRestrictedMode) {
long startTimeNs = SystemClock.uptimeNanos();
if (DEBUG_BACKUP) {
Slog.v(TAG, "bindBackupAgent: app=" + packageName + " mode=" + backupMode
@@ -14096,7 +14086,8 @@
+ app.packageName + ": " + e);
}
- BackupRecord r = new BackupRecord(app, backupMode, targetUserId, backupDestination);
+ BackupRecord r = new BackupRecord(app, backupMode, targetUserId, backupDestination,
+ useRestrictedMode);
ComponentName hostingName =
(backupMode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL
|| backupMode == ApplicationThreadConstants.BACKUP_MODE_RESTORE)
@@ -14122,8 +14113,9 @@
// process, etc, then mark it as being in full backup so that certain calls to the
// process can be blocked. This is not reset to false anywhere because we kill the
// process after the full backup is done and the ProcessRecord will vaporize anyway.
- if (UserHandle.isApp(app.uid) &&
- backupMode == ApplicationThreadConstants.BACKUP_MODE_FULL) {
+ if (UserHandle.isApp(app.uid)
+ && backupMode == ApplicationThreadConstants.BACKUP_MODE_FULL
+ && r.useRestrictedMode) {
proc.setInFullBackup(true);
}
r.app = proc;
@@ -14221,9 +14213,7 @@
final long oldIdent = Binder.clearCallingIdentity();
try {
- IBackupManager bm = IBackupManager.Stub.asInterface(
- ServiceManager.getService(Context.BACKUP_SERVICE));
- bm.agentConnectedForUser(userId, agentPackageName, agent);
+ getBackupManager().agentConnectedForUser(userId, agentPackageName, agent);
} catch (RemoteException e) {
// can't happen; the backup manager service is local
} catch (Exception e) {
@@ -18013,14 +18003,6 @@
@Override
public void addStartInfoTimestamp(int key, long timestampNs, int uid, int pid,
int userId) {
- // For the simplification, we don't support USER_ALL nor USER_CURRENT here.
- if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_CURRENT) {
- throw new IllegalArgumentException("Unsupported userId");
- }
-
- mUserController.handleIncomingUser(pid, uid, userId, true,
- ALLOW_NON_FULL, "addStartInfoTimestampSystem", null);
-
addStartInfoTimestampInternal(key, timestampNs, userId, uid);
}
@@ -19353,4 +19335,8 @@
}
return token;
}
+
+ private IBackupManager getBackupManager() {
+ return IBackupManager.Stub.asInterface(ServiceManager.getService(Context.BACKUP_SERVICE));
+ }
}
diff --git a/services/core/java/com/android/server/am/BackupRecord.java b/services/core/java/com/android/server/am/BackupRecord.java
index 0b056d7..64cc6f0 100644
--- a/services/core/java/com/android/server/am/BackupRecord.java
+++ b/services/core/java/com/android/server/am/BackupRecord.java
@@ -32,15 +32,18 @@
final int userId; // user for which backup is performed
final int backupMode; // full backup / incremental / restore
@BackupDestination final int backupDestination; // see BackupAnnotations#BackupDestination
+ final boolean useRestrictedMode; // whether the app should be put into restricted backup mode
ProcessRecord app; // where this agent is running or null
// ----- Implementation -----
- BackupRecord(ApplicationInfo _appInfo, int _backupMode, int _userId, int _backupDestination) {
+ BackupRecord(ApplicationInfo _appInfo, int _backupMode, int _userId, int _backupDestination,
+ boolean _useRestrictedMode) {
appInfo = _appInfo;
backupMode = _backupMode;
userId = _userId;
backupDestination = _backupDestination;
+ useRestrictedMode = _useRestrictedMode;
}
public String toString() {
diff --git a/services/core/java/com/android/server/am/BroadcastFilter.java b/services/core/java/com/android/server/am/BroadcastFilter.java
index e20c46c..a32d3cb 100644
--- a/services/core/java/com/android/server/am/BroadcastFilter.java
+++ b/services/core/java/com/android/server/am/BroadcastFilter.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
@@ -56,11 +57,12 @@
final boolean visibleToInstantApp;
public final boolean exported;
final int initialPriority;
+ final ApplicationInfo applicationInfo;
BroadcastFilter(IntentFilter _filter, ReceiverList _receiverList,
String _packageName, String _featureId, String _receiverId, String _requiredPermission,
int _owningUid, int _userId, boolean _instantApp, boolean _visibleToInstantApp,
- boolean _exported, ApplicationInfo applicationInfo, PlatformCompat platformCompat) {
+ boolean _exported, ApplicationInfo _applicationInfo, PlatformCompat platformCompat) {
super(_filter);
receiverList = _receiverList;
packageName = _packageName;
@@ -72,6 +74,7 @@
instantApp = _instantApp;
visibleToInstantApp = _visibleToInstantApp;
exported = _exported;
+ applicationInfo = _applicationInfo;
initialPriority = getPriority();
setPriority(calculateAdjustedPriority(owningUid, initialPriority,
applicationInfo, platformCompat));
@@ -87,6 +90,10 @@
return null;
}
+ public @NonNull ApplicationInfo getApplicationInfo() {
+ return applicationInfo;
+ }
+
@NeverCompile
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
long token = proto.start(fieldId);
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index e8ce173..a1ab1eea 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -49,6 +49,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.os.Binder;
import android.os.Bundle;
@@ -57,7 +58,6 @@
import android.util.ArrayMap;
import android.util.IntArray;
import android.util.PrintWriterPrinter;
-import android.util.SparseBooleanArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -865,25 +865,35 @@
@VisibleForTesting
static @NonNull boolean[] calculateChangeStateForReceivers(@NonNull List<Object> receivers,
long changeId, PlatformCompat platformCompat) {
- final SparseBooleanArray changeStateForUids = new SparseBooleanArray();
+ // TODO: b/371307720 - Remove this method as we are already avoiding the packagemanager
+ // calls by checking the changeId state using ApplicationInfos.
+ final ArrayMap<String, Boolean> changeStates = new ArrayMap<>();
final int count = receivers.size();
final boolean[] changeStateForReceivers = new boolean[count];
for (int i = 0; i < count; ++i) {
- final int receiverUid = getReceiverUid(receivers.get(i));
+ final ApplicationInfo receiverAppInfo = getReceiverAppInfo(receivers.get(i));
final boolean isChangeEnabled;
- final int idx = changeStateForUids.indexOfKey(receiverUid);
+ final int idx = changeStates.indexOfKey(receiverAppInfo.packageName);
if (idx >= 0) {
- isChangeEnabled = changeStateForUids.valueAt(idx);
+ isChangeEnabled = changeStates.valueAt(idx);
} else {
- isChangeEnabled = platformCompat.isChangeEnabledByUidInternalNoLogging(
- changeId, receiverUid);
- changeStateForUids.put(receiverUid, isChangeEnabled);
+ isChangeEnabled = platformCompat.isChangeEnabledInternalNoLogging(
+ changeId, receiverAppInfo);
+ changeStates.put(receiverAppInfo.packageName, isChangeEnabled);
}
changeStateForReceivers[i] = isChangeEnabled;
}
return changeStateForReceivers;
}
+ static ApplicationInfo getReceiverAppInfo(@NonNull Object receiver) {
+ if (receiver instanceof BroadcastFilter) {
+ return ((BroadcastFilter) receiver).getApplicationInfo();
+ } else {
+ return ((ResolveInfo) receiver).activityInfo.applicationInfo;
+ }
+ }
+
static int getReceiverUid(@NonNull Object receiver) {
if (receiver instanceof BroadcastFilter) {
return ((BroadcastFilter) receiver).owningUid;
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index b51db13..98f738c 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -351,7 +351,8 @@
private String[] mIsolatedEntryPointArgs;
/**
- * Process is currently hosting a backup agent for backup or restore.
+ * Process is currently hosting a backup agent for backup or restore. Note that this is only set
+ * when the process is put into restricted backup mode.
*/
@GuardedBy("mService")
private boolean mInFullBackup;
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 3644974..14d3fbc 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -393,6 +393,8 @@
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
} else if (adj < ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
adj = ProcessList.PERCEPTIBLE_LOW_APP_ADJ;
+ } else if (Flags.addModifyRawOomAdjServiceLevel() && adj < ProcessList.SERVICE_ADJ) {
+ adj = ProcessList.SERVICE_ADJ;
} else if (adj < ProcessList.CACHED_APP_MIN_ADJ) {
adj = ProcessList.CACHED_APP_MIN_ADJ;
} else if (adj < ProcessList.CACHED_APP_MAX_ADJ) {
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 711b163..c59c40f 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -260,3 +260,13 @@
description: "Use PROCESS_CAPABILITY_CPU_TIME to control unfreeze state."
bug: "370817323"
}
+
+flag {
+ name: "add_modify_raw_oom_adj_service_level"
+ namespace: "backstage_power"
+ description: "Add a SERVICE_ADJ level to the modifyRawOomAdj method"
+ bug: "374810368"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 6ba3569..5f71660 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -9224,6 +9224,9 @@
return;
}
+ // index values sent to APM are in the stream type SDK range, not *10
+ int indexMinVolCurve = MIN_STREAM_VOLUME[mStreamType];
+ int indexMaxVolCurve = MAX_STREAM_VOLUME[mStreamType];
synchronized (this) {
if (mStreamType == AudioSystem.STREAM_VOICE_CALL) {
if (MAX_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO]
@@ -9234,11 +9237,15 @@
if (!equalScoLeaVcIndexRange() && isStreamBluetoothSco(mStreamType)) {
// SCO devices have a different min index
mIndexMin = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO] * 10;
+ indexMinVolCurve = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO];
+ indexMaxVolCurve = MAX_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO];
mIndexStepFactor = 1.f;
} else if (equalScoLeaVcIndexRange() && isStreamBluetoothComm(mStreamType)) {
// For non SCO devices the stream state does not change the min index
if (mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO) {
mIndexMin = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO] * 10;
+ indexMinVolCurve = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO];
+ indexMaxVolCurve = MAX_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO];
} else {
mIndexMin = MIN_STREAM_VOLUME[mStreamType] * 10;
}
@@ -9259,7 +9266,7 @@
}
final int status = AudioSystem.initStreamVolume(
- mStreamType, mIndexMin / 10, mIndexMax / 10);
+ mStreamType, indexMinVolCurve, indexMaxVolCurve);
sVolumeLogger.enqueue(new EventLogger.StringEvent(
"updateIndexFactors() stream:" + mStreamType + " index min/max:"
+ mIndexMin / 10 + "/" + mIndexMax / 10 + " indexStepFactor:"
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index b63b07f..e92b518 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -20,6 +20,7 @@
import static android.media.AudioPlaybackConfiguration.MUTED_BY_APP_OPS;
import static android.media.AudioPlaybackConfiguration.MUTED_BY_CLIENT_VOLUME;
import static android.media.AudioPlaybackConfiguration.MUTED_BY_MASTER;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_PORT_VOLUME;
import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_MUTED;
import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_VOLUME;
import static android.media.AudioPlaybackConfiguration.MUTED_BY_VOLUME_SHAPER;
@@ -444,7 +445,7 @@
}
if (DEBUG) {
- Log.v(TAG, TextUtils.formatSimple("BLA portEvent(portId=%d, event=%s, extras=%s)",
+ Log.v(TAG, TextUtils.formatSimple("portEvent(portId=%d, event=%s, extras=%s)",
portId, AudioPlaybackConfiguration.playerStateToString(event), extras));
}
@@ -1381,6 +1382,9 @@
if ((mEventValue & MUTED_BY_VOLUME_SHAPER) != 0) {
builder.append("volumeShaper ");
}
+ if ((mEventValue & MUTED_BY_PORT_VOLUME) != 0) {
+ builder.append("portVolume ");
+ }
}
return builder.toString();
default:
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index afdc0c0..6ed1ac85 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -226,10 +226,6 @@
return BIOMETRIC_NO_HARDWARE;
}
- if (sensor.modality == TYPE_FACE && biometricCameraManager.isAnyCameraUnavailable()) {
- return BIOMETRIC_HARDWARE_NOT_DETECTED;
- }
-
final boolean wasStrongEnough =
Utils.isAtLeastStrength(sensor.oemStrength, requestedStrength);
final boolean isStrongEnough =
@@ -241,6 +237,10 @@
return BIOMETRIC_INSUFFICIENT_STRENGTH;
}
+ if (sensor.modality == TYPE_FACE && biometricCameraManager.isAnyCameraUnavailable()) {
+ return BIOMETRIC_HARDWARE_NOT_DETECTED;
+ }
+
try {
if (!sensor.impl.isHardwareDetected(opPackageName)) {
return BIOMETRIC_HARDWARE_NOT_DETECTED;
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index eeac260..6feae34 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -522,7 +522,7 @@
// b/282922910 - we don't want apps sharing system uid and targeting
// older target sdk to impact all system uid apps
if (Flags.systemUidTargetSystemSdk() && !mIsWear &&
- uid == Process.SYSTEM_UID) {
+ uid == Process.SYSTEM_UID && appInfo != null) {
appInfo.targetSdkVersion = Build.VERSION.SDK_INT;
}
return appInfo;
diff --git a/services/core/java/com/android/server/display/color/TintController.java b/services/core/java/com/android/server/display/color/TintController.java
index 716661d..68dc80f 100644
--- a/services/core/java/com/android/server/display/color/TintController.java
+++ b/services/core/java/com/android/server/display/color/TintController.java
@@ -19,6 +19,7 @@
import android.animation.ValueAnimator;
import android.content.Context;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
@@ -29,23 +30,33 @@
*/
private static final long TRANSITION_DURATION = 3000L;
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
private ValueAnimator mAnimator;
+ @GuardedBy("mLock")
private Boolean mIsActivated;
public ValueAnimator getAnimator() {
- return mAnimator;
+ synchronized (mLock) {
+ return mAnimator;
+ }
}
public void setAnimator(ValueAnimator animator) {
- mAnimator = animator;
+ synchronized (mLock) {
+ mAnimator = animator;
+ }
}
/**
* Cancel the animator if it's still running.
*/
public void cancelAnimator() {
- if (mAnimator != null) {
- mAnimator.cancel();
+ synchronized (mLock) {
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
}
}
@@ -53,22 +64,30 @@
* End the animator if it's still running, jumping to the end state.
*/
public void endAnimator() {
- if (mAnimator != null) {
- mAnimator.end();
- mAnimator = null;
+ synchronized (mLock) {
+ if (mAnimator != null) {
+ mAnimator.end();
+ mAnimator = null;
+ }
}
}
public void setActivated(Boolean isActivated) {
- mIsActivated = isActivated;
+ synchronized (mLock) {
+ mIsActivated = isActivated;
+ }
}
public boolean isActivated() {
- return mIsActivated != null && mIsActivated;
+ synchronized (mLock) {
+ return mIsActivated != null && mIsActivated;
+ }
}
public boolean isActivatedStateNotSet() {
- return mIsActivated == null;
+ synchronized (mLock) {
+ return mIsActivated == null;
+ }
}
public long getTransitionDurationMilliseconds() {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 586d594..e7ea868 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -4,6 +4,14 @@
# Important: Flags must be accessed through DisplayManagerFlags.
flag {
+ name: "is_always_on_available_api"
+ namespace: "display_manager"
+ description: "Allows querying of AOD availability"
+ bug: "324046664"
+ is_fixed_read_only: true
+}
+
+flag {
name: "enable_port_in_display_layout"
namespace: "display_manager"
description: "Allows refering to displays by port in display layout"
diff --git a/services/core/java/com/android/server/input/AppLaunchShortcutManager.java b/services/core/java/com/android/server/input/AppLaunchShortcutManager.java
index aef207f..f3820e5 100644
--- a/services/core/java/com/android/server/input/AppLaunchShortcutManager.java
+++ b/services/core/java/com/android/server/input/AppLaunchShortcutManager.java
@@ -21,6 +21,7 @@
import android.app.role.RoleManager;
import android.content.Context;
import android.content.Intent;
+import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.hardware.input.AppLaunchData;
import android.hardware.input.InputGestureData;
@@ -137,11 +138,19 @@
String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY);
String shiftName = parser.getAttributeValue(null, ATTRIBUTE_SHIFT);
String roleName = parser.getAttributeValue(null, ATTRIBUTE_ROLE);
-
- // TODO(b/358569822): Shift bookmarks to use keycode instead of shortcutChar
- int keycode = KeyEvent.KEYCODE_UNKNOWN;
String shortcut = parser.getAttributeValue(null, ATTRIBUTE_SHORTCUT);
- if (!TextUtils.isEmpty(shortcut)) {
+ int keycode;
+ int modifierState;
+ TypedArray a = mContext.getResources().obtainAttributes(parser,
+ R.styleable.Bookmark);
+ try {
+ keycode = a.getInt(R.styleable.Bookmark_keycode, KeyEvent.KEYCODE_UNKNOWN);
+ modifierState = a.getInt(R.styleable.Bookmark_modifierState, 0);
+ } finally {
+ a.recycle();
+ }
+ if (keycode == KeyEvent.KEYCODE_UNKNOWN && !TextUtils.isEmpty(shortcut)) {
+ // Fetch keycode using shortcut char
KeyEvent[] events = virtualKcm.getEvents(new char[]{shortcut.toLowerCase(
Locale.ROOT).charAt(0)});
// Single key press can generate the character
@@ -153,12 +162,17 @@
Log.w(TAG, "Keycode required for bookmark with category=" + categoryName
+ " packageName=" + packageName + " className=" + className
+ " role=" + roleName + " shiftName=" + shiftName
- + " shortcut=" + shortcut);
+ + " shortcut=" + shortcut + " modifierState=" + modifierState);
continue;
}
- final boolean isShiftShortcut = (shiftName != null && shiftName.toLowerCase(
- Locale.ROOT).equals("true"));
+ if (modifierState == 0) {
+ // Fetch modifierState using shiftName
+ boolean isShiftShortcut = shiftName != null && shiftName.toLowerCase(
+ Locale.ROOT).equals("true");
+ modifierState =
+ KeyEvent.META_META_ON | (isShiftShortcut ? KeyEvent.META_SHIFT_ON : 0);
+ }
AppLaunchData launchData = null;
if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
launchData = AppLaunchData.createLaunchDataForComponent(packageName, className);
@@ -168,11 +182,9 @@
launchData = AppLaunchData.createLaunchDataForRole(roleName);
}
if (launchData != null) {
- Log.d(TAG, "adding shortcut " + launchData + "shift="
- + isShiftShortcut + " keycode=" + keycode);
+ Log.d(TAG, "adding shortcut " + launchData + " modifierState="
+ + modifierState + " keycode=" + keycode);
// All bookmarks are based on Action key
- int modifierState =
- KeyEvent.META_META_ON | (isShiftShortcut ? KeyEvent.META_SHIFT_ON : 0);
InputGestureData bookmark = new InputGestureData.Builder()
.setTrigger(InputGestureData.createKeyTrigger(keycode, modifierState))
.setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java b/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java
new file mode 100644
index 0000000..c05f7a0
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.location.contexthub;
+
+import android.hardware.contexthub.EndpointId;
+import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.IEndpointCallback;
+import android.hardware.contexthub.Message;
+import android.hardware.contexthub.MessageDeliveryStatus;
+import android.os.RemoteException;
+
+/** IEndpointCallback implementation. */
+public class ContextHubHalEndpointCallback
+ extends android.hardware.contexthub.IEndpointCallback.Stub {
+ private final IEndpointLifecycleCallback mEndpointLifecycleCallback;
+
+ /** Interface for listening for endpoint start and stop events. */
+ public interface IEndpointLifecycleCallback {
+ /** Called when a batch of endpoints started. */
+ void onEndpointStarted(HubEndpointInfo[] endpointInfos);
+
+ /** Called when a batch of endpoints stopped. */
+ void onEndpointStopped(HubEndpointInfo.HubEndpointIdentifier[] endpointIds, byte reason);
+ }
+
+ ContextHubHalEndpointCallback(IEndpointLifecycleCallback endpointLifecycleCallback) {
+ mEndpointLifecycleCallback = endpointLifecycleCallback;
+ }
+
+ @Override
+ public void onEndpointStarted(android.hardware.contexthub.EndpointInfo[] halEndpointInfos)
+ throws RemoteException {
+ if (halEndpointInfos.length == 0) {
+ return;
+ }
+ HubEndpointInfo[] endpointInfos = new HubEndpointInfo[halEndpointInfos.length];
+ for (int i = 0; i < halEndpointInfos.length; i++) {
+ endpointInfos[i++] = new HubEndpointInfo(halEndpointInfos[i]);
+ }
+ mEndpointLifecycleCallback.onEndpointStarted(endpointInfos);
+ }
+
+ @Override
+ public void onEndpointStopped(EndpointId[] halEndpointIds, byte reason) throws RemoteException {
+ HubEndpointInfo.HubEndpointIdentifier[] endpointIds =
+ new HubEndpointInfo.HubEndpointIdentifier[halEndpointIds.length];
+ for (int i = 0; i < halEndpointIds.length; i++) {
+ endpointIds[i] = new HubEndpointInfo.HubEndpointIdentifier(halEndpointIds[i]);
+ }
+ mEndpointLifecycleCallback.onEndpointStopped(endpointIds, reason);
+ }
+
+ @Override
+ public void onMessageReceived(int i, Message message) throws RemoteException {}
+
+ @Override
+ public void onMessageDeliveryStatusReceived(int i, MessageDeliveryStatus messageDeliveryStatus)
+ throws RemoteException {}
+
+ @Override
+ public void onEndpointSessionOpenRequest(
+ int i, EndpointId endpointId, EndpointId endpointId1, String s)
+ throws RemoteException {}
+
+ @Override
+ public void onCloseEndpointSession(int i, byte b) throws RemoteException {}
+
+ @Override
+ public void onEndpointSessionOpenComplete(int i) throws RemoteException {}
+
+ @Override
+ public int getInterfaceVersion() throws RemoteException {
+ return IEndpointCallback.VERSION;
+ }
+
+ @Override
+ public String getInterfaceHash() throws RemoteException {
+ return IEndpointCallback.HASH;
+ }
+}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 88a7d08..8cf0578 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -32,6 +32,8 @@
import android.hardware.SensorPrivacyManagerInternal;
import android.hardware.contexthub.ErrorCode;
import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.IContextHubEndpoint;
+import android.hardware.contexthub.IContextHubEndpointCallback;
import android.hardware.contexthub.MessageDeliveryStatus;
import android.hardware.location.ContextHubInfo;
import android.hardware.location.ContextHubMessage;
@@ -250,6 +252,7 @@
public void handleServiceRestart() {
Log.i(TAG, "Recovering from Context Hub HAL restart...");
initExistingCallbacks();
+ mHubInfoRegistry.onHalRestart();
resetSettings();
if (Flags.reconnectHostEndpointsAfterHalRestart()) {
mClientManager.forEachClientOfHub(mContextHubId,
@@ -331,6 +334,7 @@
}
initDefaultClientMap();
+ initEndpointCallback();
initLocationSettingNotifications();
initWifiSettingNotifications();
@@ -509,6 +513,18 @@
mDefaultClientMap = Collections.unmodifiableMap(defaultClientMap);
}
+ private void initEndpointCallback() {
+ if (mHubInfoRegistry == null) {
+ return;
+ }
+ try {
+ mContextHubWrapper.registerEndpointCallback(
+ new ContextHubHalEndpointCallback(mHubInfoRegistry));
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while registering IEndpointCallback", e);
+ }
+ }
+
/**
* Initializes existing callbacks with the mContextHubWrapper for every context hub
*/
@@ -744,8 +760,20 @@
@Override
public List<HubEndpointInfo> findEndpoints(long endpointId) {
super.findEndpoints_enforcePermission();
- // TODO(b/375487784): connect this with mHubInfoRegistry
- return Collections.emptyList();
+ if (mHubInfoRegistry == null) {
+ return Collections.emptyList();
+ }
+ return mHubInfoRegistry.findEndpoints(endpointId);
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @Override
+ public IContextHubEndpoint registerEndpoint(
+ HubEndpointInfo pendingHubEndpointInfo, IContextHubEndpointCallback callback)
+ throws RemoteException {
+ super.registerEndpoint_enforcePermission();
+ // TODO(b/375487784): Implement this
+ return null;
}
/**
diff --git a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
index 68de9db..4d1000f 100644
--- a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
+++ b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
@@ -16,45 +16,144 @@
package com.android.server.location.contexthub;
+import android.hardware.contexthub.HubEndpointInfo;
import android.hardware.location.HubInfo;
import android.os.RemoteException;
+import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-class HubInfoRegistry {
+class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycleCallback {
private static final String TAG = "HubInfoRegistry";
+ private final Object mLock = new Object();
private final IContextHubWrapper mContextHubWrapper;
- private final List<HubInfo> mHubsInfo;
+ @GuardedBy("mLock")
+ private List<HubInfo> mHubsInfo;
+
+ @GuardedBy("mLock")
+ private final ArrayMap<HubEndpointInfo.HubEndpointIdentifier, HubEndpointInfo>
+ mHubEndpointInfos = new ArrayMap<>();
HubInfoRegistry(IContextHubWrapper contextHubWrapper) {
- List<HubInfo> hubInfos;
mContextHubWrapper = contextHubWrapper;
+ refreshCachedHubs();
+ refreshCachedEndpoints();
+ }
+
+ /** Retrieve the list of hubs available. */
+ List<HubInfo> getHubs() {
+ synchronized (mLock) {
+ return mHubsInfo;
+ }
+ }
+
+ private void refreshCachedHubs() {
+ List<HubInfo> hubInfos;
try {
hubInfos = mContextHubWrapper.getHubs();
} catch (RemoteException e) {
Log.e(TAG, "RemoteException while getting Hub info", e);
hubInfos = Collections.emptyList();
}
- mHubsInfo = hubInfos;
+
+ synchronized (mLock) {
+ mHubsInfo = hubInfos;
+ }
}
- /** Retrieve the list of hubs available. */
- List<HubInfo> getHubs() {
- return mHubsInfo;
+ private void refreshCachedEndpoints() {
+ List<HubEndpointInfo> endpointInfos;
+ try {
+ endpointInfos = mContextHubWrapper.getEndpoints();
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while getting Hub info", e);
+ endpointInfos = Collections.emptyList();
+ }
+
+ synchronized (mLock) {
+ mHubEndpointInfos.clear();
+ for (HubEndpointInfo endpointInfo : endpointInfos) {
+ mHubEndpointInfos.put(endpointInfo.getIdentifier(), endpointInfo);
+ }
+ }
+ }
+
+ /** Invoked when HAL restarts */
+ public void onHalRestart() {
+ synchronized (mLock) {
+ refreshCachedHubs();
+ refreshCachedEndpoints();
+ }
+ }
+
+ @Override
+ public void onEndpointStarted(HubEndpointInfo[] endpointInfos) {
+ synchronized (mLock) {
+ for (HubEndpointInfo endpointInfo : endpointInfos) {
+ mHubEndpointInfos.remove(endpointInfo.getIdentifier());
+ mHubEndpointInfos.put(endpointInfo.getIdentifier(), endpointInfo);
+ }
+ }
+ }
+
+ @Override
+ public void onEndpointStopped(
+ HubEndpointInfo.HubEndpointIdentifier[] endpointIds, byte reason) {
+ synchronized (mLock) {
+ for (HubEndpointInfo.HubEndpointIdentifier endpointId : endpointIds) {
+ mHubEndpointInfos.remove(endpointId);
+ }
+ }
+ }
+
+ /** Return a list of {@link HubEndpointInfo} that represents endpoints with the matching id. */
+ public List<HubEndpointInfo> findEndpoints(long endpointIdQuery) {
+ List<HubEndpointInfo> searchResult = new ArrayList<>();
+ synchronized (mLock) {
+ for (HubEndpointInfo.HubEndpointIdentifier endpointId : mHubEndpointInfos.keySet()) {
+ if (endpointId.getEndpoint() == endpointIdQuery) {
+ searchResult.add(mHubEndpointInfos.get(endpointId));
+ }
+ }
+ }
+ return searchResult;
}
void dump(IndentingPrintWriter ipw) {
+ synchronized (mLock) {
+ dumpLocked(ipw);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void dumpLocked(IndentingPrintWriter ipw) {
ipw.println(TAG);
ipw.increaseIndent();
+ ipw.println("Hubs");
for (HubInfo hubInfo : mHubsInfo) {
ipw.println(hubInfo);
}
ipw.decreaseIndent();
+
+ ipw.println();
+
+ ipw.increaseIndent();
+ ipw.println("Endpoints");
+ for (HubEndpointInfo endpointInfo : mHubEndpointInfos.values()) {
+ ipw.println(endpointInfo);
+ }
+ ipw.decreaseIndent();
+
+ ipw.println();
}
+
}
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 6656a6f..9b729eb 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import android.chre.flags.Flags;
import android.hardware.contexthub.HostEndpointInfo;
+import android.hardware.contexthub.HubEndpointInfo;
import android.hardware.contexthub.MessageDeliveryStatus;
import android.hardware.contexthub.NanSessionRequest;
import android.hardware.contexthub.V1_0.ContextHub;
@@ -229,6 +230,15 @@
return Collections.emptyList();
}
+ /** Calls the appropriate getEndpoints function depending on the HAL version. */
+ public List<HubEndpointInfo> getEndpoints() throws RemoteException {
+ return Collections.emptyList();
+ }
+
+ /** Calls the appropriate registerEndpointCallback function depending on the HAL version. */
+ public void registerEndpointCallback(android.hardware.contexthub.IEndpointCallback cb)
+ throws RemoteException {}
+
/**
* @return True if this version of the Contexthub HAL supports Location setting notifications.
*/
@@ -622,6 +632,45 @@
return retVal;
}
+ @Override
+ public List<HubEndpointInfo> getEndpoints() throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return Collections.emptyList();
+ }
+
+ List<HubEndpointInfo> retVal = new ArrayList<>();
+ final List<android.hardware.contexthub.EndpointInfo> halEndpointInfos =
+ hub.getEndpoints();
+ for (android.hardware.contexthub.EndpointInfo halEndpointInfo : halEndpointInfos) {
+ /* HAL -> API Type conversion */
+ final HubEndpointInfo endpointInfo = new HubEndpointInfo(halEndpointInfo);
+ if (DEBUG) {
+ Log.i(TAG, "getEndpoints: endpointInfo=" + endpointInfo);
+ }
+ retVal.add(endpointInfo);
+ }
+
+ if (DEBUG) {
+ Log.i(TAG, "getEndpoints: total count=" + retVal.size());
+ }
+ return retVal;
+ }
+
+ @Override
+ public void registerEndpointCallback(android.hardware.contexthub.IEndpointCallback cb)
+ throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+
+ if (DEBUG) {
+ Log.i(TAG, "registerEndpointCallback: cb=" + cb);
+ }
+ hub.registerEndpointCallback(cb);
+ }
+
public boolean supportsLocationSettingNotifications() {
return true;
}
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index c460465..fce008c 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -175,7 +175,7 @@
private void maybeStopMediaProjection(int reason) {
synchronized (mLock) {
- if (!mMediaProjectionStopController.isExemptFromStopping(mProjectionGrant)) {
+ if (!mMediaProjectionStopController.isExemptFromStopping(mProjectionGrant, reason)) {
Slog.d(TAG, "Content Recording: Stopping MediaProjection due to "
+ MediaProjectionStopController.stopReasonToString(reason));
mProjectionGrant.stop();
@@ -1272,6 +1272,10 @@
return mDisplayId;
}
+ long getCreateTimeMillis() {
+ return mCreateTimeMs;
+ }
+
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
@Override
public boolean isValid() {
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java b/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java
index f5b26c4..c018e6b 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java
@@ -27,6 +27,7 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
+import android.os.SystemClock;
import android.provider.Settings;
import android.telecom.TelecomManager;
import android.telephony.TelephonyCallback;
@@ -46,6 +47,8 @@
private static final String TAG = "MediaProjectionStopController";
@VisibleForTesting
+ static final int STOP_REASON_UNKNOWN = 0;
+ @VisibleForTesting
static final int STOP_REASON_KEYGUARD = 1;
@VisibleForTesting
static final int STOP_REASON_CALL_END = 2;
@@ -61,6 +64,7 @@
private final ContentResolver mContentResolver;
private boolean mIsInCall;
+ private long mLastCallStartTimeMillis;
public MediaProjectionStopController(Context context, Consumer<Integer> stopReasonConsumer) {
mStopReasonConsumer = stopReasonConsumer;
@@ -95,8 +99,8 @@
* Checks whether the given projection grant is exempt from stopping restrictions.
*/
public boolean isExemptFromStopping(
- MediaProjectionManagerService.MediaProjection projectionGrant) {
- return isExempt(projectionGrant, false);
+ MediaProjectionManagerService.MediaProjection projectionGrant, int stopReason) {
+ return isExempt(projectionGrant, stopReason, false);
}
/**
@@ -110,7 +114,8 @@
* MediaProjection session
*/
private boolean isExempt(
- MediaProjectionManagerService.MediaProjection projectionGrant, boolean forStart) {
+ MediaProjectionManagerService.MediaProjection projectionGrant, int stopReason,
+ boolean forStart) {
if (projectionGrant == null || projectionGrant.packageName == null) {
return true;
}
@@ -151,6 +156,14 @@
return true;
}
+ if (stopReason == STOP_REASON_CALL_END
+ && projectionGrant.getCreateTimeMillis() < mLastCallStartTimeMillis) {
+ Slog.v(TAG,
+ "Continuing MediaProjection as (phone) call started after MediaProjection was"
+ + " created.");
+ return true;
+ }
+
return false;
}
@@ -167,7 +180,7 @@
return false;
}
- if (isExempt(projectionGrant, true)) {
+ if (isExempt(projectionGrant, STOP_REASON_UNKNOWN, true)) {
return false;
}
return true;
@@ -188,9 +201,13 @@
return;
}
boolean isInCall = mTelecomManager.isInCall();
+ if (isInCall) {
+ mLastCallStartTimeMillis = SystemClock.uptimeMillis();
+ }
if (isInCall == mIsInCall) {
return;
}
+
if (mIsInCall && !isInCall) {
mStopReasonConsumer.accept(STOP_REASON_CALL_END);
}
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index c5c8a5e..1f8a200 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -262,7 +262,7 @@
// TODO: implement
}
@Override
- public SoundProfile getSoundProfileById(String id) {
+ public SoundProfile getSoundProfile(int type, String id) {
return null;
}
@Override
@@ -313,6 +313,15 @@
}
@Override
+ public List<String> getSoundProfileAllowList() {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public void setSoundProfileAllowList(List<String> packages) {
+ }
+
+ @Override
public boolean isSupported() {
return false;
}
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 9f4b9f1..6d54be8 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -58,6 +58,7 @@
import android.os.storage.VolumeInfo;
import android.provider.DeviceConfig;
import android.stats.storage.StorageEnums;
+import android.text.TextUtils;
import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
@@ -355,7 +356,8 @@
@Nullable int[] userIds,
@Nullable int[] instantUserIds,
@Nullable SparseArray<int[]> broadcastAllowList,
- @NonNull AndroidPackage pkg) {
+ @NonNull AndroidPackage pkg,
+ @NonNull String[] sharedUidPackages) {
final boolean isForWholeApp = componentNames.contains(packageName);
if (isForWholeApp || !android.content.pm.Flags.reduceBroadcastsForComponentStateChanges()) {
sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp, componentNames,
@@ -374,20 +376,36 @@
exportedComponentNames.removeAll(notExportedComponentNames);
if (!notExportedComponentNames.isEmpty()) {
- // Limit sending of the PACKAGE_CHANGED broadcast to only the system and the
- // application itself when the component is not exported.
+ // Limit sending of the PACKAGE_CHANGED broadcast to only the system, the application
+ // itself and applications with the same UID when the component is not exported.
// First, send the PACKAGE_CHANGED broadcast to the system.
- sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
- notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
- broadcastAllowList, "android" /* targetPackageName */,
- new String[]{PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED});
+ if (!TextUtils.equals(packageName, "android")) {
+ sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
+ notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
+ broadcastAllowList, "android" /* targetPackageName */,
+ new String[]{
+ PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED});
+ }
// Second, send the PACKAGE_CHANGED broadcast to the application itself.
sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
broadcastAllowList, packageName /* targetPackageName */,
null /* requiredPermissions */);
+
+ // Third, send the PACKAGE_CHANGED broadcast to the applications with the same UID.
+ for (int i = 0; i < sharedUidPackages.length; i++) {
+ final String sharedPackage = sharedUidPackages[i];
+ if (TextUtils.equals(packageName, sharedPackage)) {
+ continue;
+ }
+ sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
+ notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
+ broadcastAllowList, sharedPackage /* targetPackageName */,
+ null /* requiredPermissions */);
+ }
+
}
if (!exportedComponentNames.isEmpty()) {
@@ -936,7 +954,8 @@
isInstantApp ? null : snapshot.getVisibilityAllowLists(packageName, userIds);
mHandler.post(() -> sendPackageChangedBroadcastInternal(
packageName, dontKillApp, componentNames, packageUid, reason, userIds,
- instantUserIds, broadcastAllowList, setting.getPkg()));
+ instantUserIds, broadcastAllowList, setting.getPkg(),
+ snapshot.getSharedUserPackagesForPackage(packageName, userId)));
mPackageMonitorCallbackHelper.notifyPackageChanged(packageName, dontKillApp, componentNames,
packageUid, reason, userIds, instantUserIds, broadcastAllowList, mHandler);
}
diff --git a/services/core/java/com/android/server/pm/InstallDependencyHelper.java b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
index 745665b..527d680 100644
--- a/services/core/java/com/android/server/pm/InstallDependencyHelper.java
+++ b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
@@ -17,51 +17,240 @@
package com.android.server.pm;
import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
+import static android.os.Process.SYSTEM_UID;
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
import android.content.pm.SharedLibraryInfo;
+import android.content.pm.dependencyinstaller.DependencyInstallerCallback;
+import android.content.pm.dependencyinstaller.IDependencyInstallerCallback;
+import android.content.pm.dependencyinstaller.IDependencyInstallerService;
import android.content.pm.parsing.PackageLite;
+import android.os.Handler;
import android.os.OutcomeReceiver;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
import java.util.List;
+import java.util.concurrent.TimeUnit;
/**
* Helper class to interact with SDK Dependency Installer service.
*/
public class InstallDependencyHelper {
- private final SharedLibrariesImpl mSharedLibraries;
+ private static final String TAG = InstallDependencyHelper.class.getSimpleName();
+ private static final boolean DEBUG = true;
+ private static final String ACTION_INSTALL_DEPENDENCY =
+ "android.intent.action.INSTALL_DEPENDENCY";
+ // The maximum amount of time to wait before the system unbinds from the verifier.
+ private static final long UNBIND_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(6);
+ private static final long REQUEST_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(1);
- InstallDependencyHelper(SharedLibrariesImpl sharedLibraries) {
+ private final SharedLibrariesImpl mSharedLibraries;
+ private final Context mContext;
+ private final Object mRemoteServiceLock = new Object();
+
+ @GuardedBy("mRemoteServiceLock")
+ private ServiceConnector<IDependencyInstallerService> mRemoteService = null;
+
+ InstallDependencyHelper(Context context, SharedLibrariesImpl sharedLibraries) {
+ mContext = context;
mSharedLibraries = sharedLibraries;
}
- void resolveLibraryDependenciesIfNeeded(PackageLite pkg,
- OutcomeReceiver<Void, PackageManagerException> callback) {
- final List<SharedLibraryInfo> missing;
+ void resolveLibraryDependenciesIfNeeded(PackageLite pkg, Computer snapshot, int userId,
+ Handler handler, OutcomeReceiver<Void, PackageManagerException> origCallback) {
+ CallOnceProxy callback = new CallOnceProxy(handler, origCallback);
try {
- missing = mSharedLibraries.collectMissingSharedLibraryInfos(pkg);
+ resolveLibraryDependenciesIfNeededInternal(pkg, snapshot, userId, handler, callback);
} catch (PackageManagerException e) {
callback.onError(e);
- return;
+ } catch (Exception e) {
+ onError(callback, e.getMessage());
}
+ }
+
+
+ private void resolveLibraryDependenciesIfNeededInternal(PackageLite pkg, Computer snapshot,
+ int userId, Handler handler, CallOnceProxy callback) throws PackageManagerException {
+ final List<SharedLibraryInfo> missing =
+ mSharedLibraries.collectMissingSharedLibraryInfos(pkg);
if (missing.isEmpty()) {
+ if (DEBUG) {
+ Slog.i(TAG, "No missing dependency for " + pkg);
+ }
// No need for dependency resolution. Move to installation directly.
callback.onResult(null);
return;
}
- try {
- bindToDependencyInstaller();
- } catch (Exception e) {
- PackageManagerException pe = new PackageManagerException(
- INSTALL_FAILED_MISSING_SHARED_LIBRARY, e.getMessage());
- callback.onError(pe);
+ if (!bindToDependencyInstallerIfNeeded(userId, handler, snapshot)) {
+ onError(callback, "Dependency Installer Service not found");
+ return;
+ }
+
+ IDependencyInstallerCallback serviceCallback = new IDependencyInstallerCallback.Stub() {
+ @Override
+ public void onAllDependenciesResolved(int[] sessionIds) throws RemoteException {
+ // TODO(b/372862145): Implement waiting for sessions to finish installation
+ callback.onResult(null);
+ }
+
+ @Override
+ public void onFailureToResolveAllDependencies() throws RemoteException {
+ onError(callback, "Failed to resolve all dependencies automatically");
+ }
+ };
+
+ boolean scheduleSuccess;
+ synchronized (mRemoteServiceLock) {
+ scheduleSuccess = mRemoteService.run(service -> {
+ service.onDependenciesRequired(missing,
+ new DependencyInstallerCallback(serviceCallback.asBinder()));
+ });
+ }
+ if (!scheduleSuccess) {
+ onError(callback, "Failed to schedule job on Dependency Installer Service");
}
}
- private void bindToDependencyInstaller() {
- throw new IllegalStateException("Failed to bind to Dependency Installer");
+ private void onError(CallOnceProxy callback, String msg) {
+ PackageManagerException pe = new PackageManagerException(
+ INSTALL_FAILED_MISSING_SHARED_LIBRARY, msg);
+ callback.onError(pe);
}
+ private boolean bindToDependencyInstallerIfNeeded(int userId, Handler handler,
+ Computer snapshot) {
+ synchronized (mRemoteServiceLock) {
+ if (mRemoteService != null) {
+ if (DEBUG) {
+ Slog.i(TAG, "DependencyInstallerService already bound");
+ }
+ return true;
+ }
+ }
+ Intent serviceIntent = new Intent(ACTION_INSTALL_DEPENDENCY);
+ // TODO(b/372862145): Use RoleManager to find the package name
+ List<ResolveInfo> resolvedIntents = snapshot.queryIntentServicesInternal(
+ serviceIntent, /*resolvedType=*/ null, /*flags=*/0,
+ userId, SYSTEM_UID, Process.INVALID_PID,
+ /*includeInstantApps*/ false, /*resolveForStart*/ false);
+
+ if (resolvedIntents.isEmpty()) {
+ return false;
+ }
+
+
+ ResolveInfo resolveInfo = resolvedIntents.getFirst();
+ ComponentName componentName = resolveInfo.getComponentInfo().getComponentName();
+ serviceIntent.setComponent(componentName);
+
+ ServiceConnector<IDependencyInstallerService> serviceConnector =
+ new ServiceConnector.Impl<IDependencyInstallerService>(mContext, serviceIntent,
+ Context.BIND_AUTO_CREATE, userId,
+ IDependencyInstallerService.Stub::asInterface) {
+ @Override
+ protected Handler getJobHandler() {
+ return handler;
+ }
+
+ @Override
+ protected long getRequestTimeoutMs() {
+ return REQUEST_TIMEOUT_MILLIS;
+ }
+
+ @Override
+ protected long getAutoDisconnectTimeoutMs() {
+ return UNBIND_TIMEOUT_MILLIS;
+ }
+ };
+
+
+ synchronized (mRemoteServiceLock) {
+ // Some other thread managed to connect to the service first
+ if (mRemoteService != null) {
+ return true;
+ }
+ mRemoteService = serviceConnector;
+ mRemoteService.setServiceLifecycleCallbacks(
+ new ServiceConnector.ServiceLifecycleCallbacks<>() {
+ @Override
+ public void onDisconnected(@NonNull IDependencyInstallerService service) {
+ Slog.w(TAG,
+ "DependencyInstallerService " + componentName + " is disconnected");
+ destroy();
+ }
+
+ @Override
+ public void onBinderDied() {
+ Slog.w(TAG, "DependencyInstallerService " + componentName + " has died");
+ destroy();
+ }
+
+ private void destroy() {
+ synchronized (mRemoteServiceLock) {
+ if (mRemoteService != null) {
+ mRemoteService.unbind();
+ mRemoteService = null;
+ }
+ }
+ }
+
+ });
+ AndroidFuture<IDependencyInstallerService> unusedFuture = mRemoteService.connect();
+ }
+ return true;
+ }
+
+ /**
+ * Ensure we call one of the outcomes only once, on the right handler.
+ *
+ * Repeated calls will be no-op.
+ */
+ private static class CallOnceProxy implements OutcomeReceiver<Void, PackageManagerException> {
+ private final Handler mHandler;
+ private final OutcomeReceiver<Void, PackageManagerException> mCallback;
+ @GuardedBy("this")
+ private boolean mCalled = false;
+
+ CallOnceProxy(Handler handler, OutcomeReceiver<Void, PackageManagerException> callback) {
+ mHandler = handler;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onResult(Void result) {
+ synchronized (this) {
+ if (!mCalled) {
+ mHandler.post(() -> {
+ mCallback.onResult(null);
+ });
+ mCalled = true;
+ }
+ }
+ }
+
+ @Override
+ public void onError(@NonNull PackageManagerException error) {
+ synchronized (this) {
+ if (!mCalled) {
+ mHandler.post(() -> {
+ mCallback.onError(error);
+ });
+ mCalled = true;
+ }
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index eb70748..9b44f93 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -347,7 +347,7 @@
synchronized (mVerificationPolicyPerUser) {
mVerificationPolicyPerUser.put(USER_SYSTEM, DEFAULT_VERIFICATION_POLICY);
}
- mInstallDependencyHelper = new InstallDependencyHelper(
+ mInstallDependencyHelper = new InstallDependencyHelper(mContext,
mPm.mInjector.getSharedLibrariesImpl());
LocalServices.getService(SystemServiceManager.class).startService(
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index e156b31..505b7e6 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -3428,8 +3428,8 @@
private void resolveLibraryDependenciesIfNeeded() {
synchronized (mLock) {
- // TODO(b/372862145): Callback should be called on a handler passed as parameter
mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(mPackageLite,
+ mPm.snapshotComputer(), userId, mHandler,
new OutcomeReceiver<>() {
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 7ef3582..961b4b3c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3606,6 +3606,13 @@
case "--force-verification":
sessionParams.setForceVerification();
break;
+ case "--disable-auto-install-dependencies":
+ if (Flags.sdkDependencyInstaller()) {
+ sessionParams.setEnableAutoInstallDependencies(false);
+ } else {
+ throw new IllegalArgumentException("Unknown option " + opt);
+ }
+ break;
default:
throw new IllegalArgumentException("Unknown option " + opt);
}
@@ -4894,6 +4901,10 @@
+ "#compiler_filters");
pw.println(" or 'skip'");
pw.println(" --force-verification: if set, enable the verification for this install");
+ if (Flags.sdkDependencyInstaller()) {
+ pw.println(" --disable-auto-install-dependencies: if set, any missing shared");
+ pw.println(" library dependencies will not be auto-installed");
+ }
pw.println("");
pw.println(" install-existing [--user USER_ID|all|current]");
pw.println(" [--instant] [--full] [--wait] [--restrict-permissions] PACKAGE");
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index fc54f68..17d7a14 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -1017,10 +1017,11 @@
boolean isSdkOrStatic = libraryType.equals(LIBRARY_TYPE_SDK)
|| libraryType.equals(LIBRARY_TYPE_STATIC);
if (isSdkOrStatic && outMissingSharedLibraryInfos != null) {
- // TODO(b/372862145): Pass the CertDigest too
// If Dependency Installation is supported, try that instead of failing.
+ final List<String> libCertDigests = Arrays.asList(requiredCertDigests[i]);
SharedLibraryInfo missingLibrary = new SharedLibraryInfo(
- libName, libVersion, SharedLibraryInfo.TYPE_SDK_PACKAGE
+ libName, libVersion, SharedLibraryInfo.TYPE_SDK_PACKAGE,
+ libCertDigests
);
outMissingSharedLibraryInfos.add(missingLibrary);
} else {
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index 4f67318..c9f66eb 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -29,6 +29,7 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
+import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Icon;
import android.hardware.input.AppLaunchData;
@@ -65,6 +66,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -84,6 +86,7 @@
private static final String ATTRIBUTE_PACKAGE = "package";
private static final String ATTRIBUTE_CLASS = "class";
private static final String ATTRIBUTE_SHORTCUT = "shortcut";
+ private static final String ATTRIBUTE_KEYCODE = "keycode";
private static final String ATTRIBUTE_CATEGORY = "category";
private static final String ATTRIBUTE_SHIFT = "shift";
private static final String ATTRIBUTE_ROLE = "role";
@@ -167,6 +170,9 @@
}, UserHandle.ALL);
mCurrentUser = currentUser;
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
+ }
+
+ void onSystemReady() {
loadShortcuts();
}
@@ -335,6 +341,7 @@
try {
XmlResourceParser parser = mContext.getResources().getXml(R.xml.bookmarks);
XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
+ KeyCharacterMap virtualKcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
while (true) {
XmlUtils.nextElement(parser);
@@ -353,15 +360,36 @@
String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY);
String shiftName = parser.getAttributeValue(null, ATTRIBUTE_SHIFT);
String roleName = parser.getAttributeValue(null, ATTRIBUTE_ROLE);
+ final int keycode;
+ final int modifierState;
+ TypedArray a = mContext.getResources().obtainAttributes(parser,
+ R.styleable.Bookmark);
+ try {
+ keycode = a.getInt(R.styleable.Bookmark_keycode, KeyEvent.KEYCODE_UNKNOWN);
+ modifierState = a.getInt(R.styleable.Bookmark_modifierState, 0);
+ } finally {
+ a.recycle();
+ }
+ if (TextUtils.isEmpty(shortcutName) && keycode != KeyEvent.KEYCODE_UNKNOWN) {
+ // Try to find shortcutChar using keycode
+ shortcutName = String.valueOf(virtualKcm.getDisplayLabel(keycode)).toLowerCase(
+ Locale.ROOT);
+ }
if (TextUtils.isEmpty(shortcutName)) {
Log.w(TAG, "Shortcut required for bookmark with category=" + categoryName
+ " packageName=" + packageName + " className=" + className
- + " role=" + roleName + "shiftName=" + shiftName);
+ + " role=" + roleName + " shiftName=" + shiftName + " keycode= "
+ + keycode + " modifierState= " + modifierState);
continue;
}
- final boolean isShiftShortcut = (shiftName != null && shiftName.equals("true"));
+ final boolean isShiftShortcut;
+ if (!TextUtils.isEmpty(shiftName)) {
+ isShiftShortcut = shiftName.equals("true");
+ } else {
+ isShiftShortcut = (modifierState & KeyEvent.META_SHIFT_ON) != 0;
+ }
if (modifierShortcutManagerRefactor()) {
final char shortcutChar = shortcutName.charAt(0);
@@ -376,7 +404,7 @@
bookmark = new RoleBookmark(shortcutChar, isShiftShortcut, roleName);
}
if (bookmark != null) {
- Log.d(TAG, "adding shortcut " + bookmark + "shift="
+ Log.d(TAG, "adding shortcut " + bookmark + " shift="
+ isShiftShortcut + " char=" + shortcutChar);
mBookmarks.put(new Pair<>(shortcutChar, isShiftShortcut), bookmark);
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index dda5bcf..85e7cfe 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -6610,6 +6610,7 @@
// In normal flow, systemReady is called before other system services are ready.
// So it is better not to bind keyguard here.
mKeyguardDelegate.onSystemReady();
+ mModifierShortcutManager.onSystemReady();
mVrManagerInternal = LocalServices.getService(VrManagerInternal.class);
if (mVrManagerInternal != null) {
diff --git a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
index 07478e3..9e75cf2 100644
--- a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
@@ -37,8 +37,11 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.Locale;
import java.util.NoSuchElementException;
@@ -60,6 +63,9 @@
* used for another vibration.
*/
void onSessionReleased(long sessionId);
+
+ /** Request the manager to trigger a vibration within this session. */
+ void vibrate(long sessionId, CallerInfo callerInfo, CombinedVibration vibration);
}
private final Object mLock = new Object();
@@ -71,7 +77,9 @@
private final IVibrationSessionCallback mCallback;
private final CallerInfo mCallerInfo;
private final VibratorManagerHooks mManagerHooks;
+ private final DeviceAdapter mDeviceAdapter;
private final Handler mHandler;
+ private final List<DebugInfo> mVibrations = new ArrayList<>();
@GuardedBy("mLock")
private Status mStatus = Status.RUNNING;
@@ -83,24 +91,28 @@
private long mEndUptime;
@GuardedBy("mLock")
private long mEndTime; // for debugging
+ @GuardedBy("mLock")
+ private VibrationStepConductor mConductor;
VendorVibrationSession(@NonNull CallerInfo callerInfo, @NonNull Handler handler,
- @NonNull VibratorManagerHooks managerHooks, @NonNull int[] vibratorIds,
+ @NonNull VibratorManagerHooks managerHooks, @NonNull DeviceAdapter deviceAdapter,
@NonNull IVibrationSessionCallback callback) {
mCreateUptime = SystemClock.uptimeMillis();
mCreateTime = System.currentTimeMillis();
- mVibratorIds = vibratorIds;
+ mVibratorIds = deviceAdapter.getAvailableVibratorIds();
mHandler = handler;
mCallback = callback;
mCallerInfo = callerInfo;
mManagerHooks = managerHooks;
+ mDeviceAdapter = deviceAdapter;
CancellationSignal.fromTransport(mCancellationSignal).setOnCancelListener(this);
}
@Override
public void vibrate(CombinedVibration vibration, String reason) {
- // TODO(b/345414356): implement vibration support
- throw new UnsupportedOperationException("Vendor session vibrations not yet implemented");
+ CallerInfo vibrationCallerInfo = new CallerInfo(mCallerInfo.attrs, mCallerInfo.uid,
+ mCallerInfo.deviceId, mCallerInfo.opPkg, reason);
+ mManagerHooks.vibrate(mSessionId, vibrationCallerInfo, vibration);
}
@Override
@@ -146,7 +158,7 @@
public DebugInfo getDebugInfo() {
synchronized (mLock) {
return new DebugInfoImpl(mStatus, mCallerInfo, mCreateUptime, mCreateTime, mStartTime,
- mEndUptime, mEndTime);
+ mEndUptime, mEndTime, mVibrations);
}
}
@@ -200,12 +212,12 @@
@Override
public void notifyVibratorCallback(int vibratorId, long vibrationId) {
- // TODO(b/345414356): implement vibration support
+ // Ignore it, the session vibration playback doesn't depend on HAL timings
}
@Override
public void notifySyncedVibratorsCallback(long vibrationId) {
- // TODO(b/345414356): implement vibration support
+ // Ignore it, the session vibration playback doesn't depend on HAL timings
}
@Override
@@ -214,8 +226,9 @@
// If end was not requested then the HAL has cancelled the session.
maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON);
maybeSetStatusToRequestedLocked();
+ clearVibrationConductor();
}
- mManagerHooks.onSessionReleased(mSessionId);
+ mHandler.post(() -> mManagerHooks.onSessionReleased(mSessionId));
}
@Override
@@ -228,7 +241,8 @@
/* includeDate= */ true))
+ ", status: " + mStatus.name().toLowerCase(Locale.ROOT)
+ ", callerInfo: " + mCallerInfo
- + ", vibratorIds: " + Arrays.toString(mVibratorIds);
+ + ", vibratorIds: " + Arrays.toString(mVibratorIds)
+ + ", vibrations: " + mVibrations;
}
}
@@ -254,6 +268,13 @@
return mVibratorIds;
}
+ @VisibleForTesting
+ public List<DebugInfo> getVibrations() {
+ synchronized (mLock) {
+ return new ArrayList<>(mVibrations);
+ }
+ }
+
public ICancellationSignal getCancellationSignal() {
return mCancellationSignal;
}
@@ -278,7 +299,39 @@
}
if (isAlreadyEnded) {
// Session already ended, make sure we end it in the HAL.
- mManagerHooks.endSession(mSessionId, /* shouldAbort= */ true);
+ mHandler.post(() -> mManagerHooks.endSession(mSessionId, /* shouldAbort= */ true));
+ }
+ }
+
+ public void notifyVibrationAttempt(DebugInfo vibrationDebugInfo) {
+ mVibrations.add(vibrationDebugInfo);
+ }
+
+ @Nullable
+ public VibrationStepConductor clearVibrationConductor() {
+ synchronized (mLock) {
+ VibrationStepConductor conductor = mConductor;
+ if (conductor != null) {
+ mVibrations.add(conductor.getVibration().getDebugInfo());
+ }
+ mConductor = null;
+ return conductor;
+ }
+ }
+
+ public DeviceAdapter getDeviceAdapter() {
+ return mDeviceAdapter;
+ }
+
+ public boolean maybeSetVibrationConductor(VibrationStepConductor conductor) {
+ synchronized (mLock) {
+ if (mConductor != null) {
+ Slog.d(TAG, "Vibration session still dispatching previous vibration,"
+ + " new vibration ignored");
+ return false;
+ }
+ mConductor = conductor;
+ return true;
}
}
@@ -296,7 +349,7 @@
}
}
if (shouldTriggerSessionHook) {
- mManagerHooks.endSession(mSessionId, shouldAbort);
+ mHandler.post(() -> mManagerHooks.endSession(mSessionId, shouldAbort));
}
}
@@ -309,6 +362,11 @@
mEndStatusRequest = status;
mEndTime = System.currentTimeMillis();
mEndUptime = SystemClock.uptimeMillis();
+ if (mConductor != null) {
+ // Vibration is being dispatched when session end was requested, cancel it.
+ mConductor.notifyCancelled(new Vibration.EndInfo(status),
+ /* immediate= */ status != Status.FINISHED);
+ }
if (isStarted()) {
// Only trigger "finishing" callback if session started.
// Run client callback in separate thread.
@@ -377,6 +435,7 @@
static final class DebugInfoImpl implements VibrationSession.DebugInfo {
private final Status mStatus;
private final CallerInfo mCallerInfo;
+ private final List<DebugInfo> mVibrations;
private final long mCreateUptime;
private final long mCreateTime;
@@ -385,7 +444,7 @@
private final long mDurationMs;
DebugInfoImpl(Status status, CallerInfo callerInfo, long createUptime, long createTime,
- long startTime, long endUptime, long endTime) {
+ long startTime, long endUptime, long endTime, List<DebugInfo> vibrations) {
mStatus = status;
mCallerInfo = callerInfo;
mCreateUptime = createUptime;
@@ -393,6 +452,7 @@
mStartTime = startTime;
mEndTime = endTime;
mDurationMs = endUptime > 0 ? endUptime - createUptime : -1;
+ mVibrations = vibrations == null ? new ArrayList<>() : new ArrayList<>(vibrations);
}
@Override
@@ -418,6 +478,9 @@
@Override
public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
+ for (DebugInfo vibration : mVibrations) {
+ vibration.logMetrics(statsLogger);
+ }
}
@Override
@@ -448,6 +511,14 @@
pw.println("endTime = " + (mEndTime == 0 ? null
: formatTime(mEndTime, /*includeDate=*/ true)));
pw.println("callerInfo = " + mCallerInfo);
+
+ pw.println("vibrations:");
+ pw.increaseIndent();
+ for (DebugInfo vibration : mVibrations) {
+ vibration.dump(pw);
+ }
+ pw.decreaseIndent();
+
pw.decreaseIndent();
}
@@ -477,6 +548,12 @@
" | %s (uid=%d, deviceId=%d) | reason: %s",
mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.deviceId, mCallerInfo.reason);
pw.println(timingsStr + paramStr + audioUsageStr + callerStr);
+
+ pw.increaseIndent();
+ for (DebugInfo vibration : mVibrations) {
+ vibration.dumpCompact(pw);
+ }
+ pw.decreaseIndent();
}
@Override
@@ -487,7 +564,8 @@
/* includeDate= */ true))
+ ", durationMs: " + mDurationMs
+ ", status: " + mStatus.name().toLowerCase(Locale.ROOT)
- + ", callerInfo: " + mCallerInfo;
+ + ", callerInfo: " + mCallerInfo
+ + ", vibrations: " + mVibrations;
}
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 1030df6..cc163db 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -106,7 +106,7 @@
private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
private static final String VIBRATOR_CONTROL_SERVICE =
"android.frameworks.vibrator.IVibratorControlService/default";
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
private static final VibrationAttributes DEFAULT_ATTRIBUTES =
new VibrationAttributes.Builder().build();
private static final int ATTRIBUTES_ALL_BYPASS_FLAGS =
@@ -610,6 +610,11 @@
logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_ERROR_TOKEN);
return null;
}
+ enforceUpdateAppOpsStatsPermission(uid);
+ if (!isEffectValid(effect)) {
+ logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_UNSUPPORTED);
+ return null;
+ }
if (effect.hasVendorEffects()) {
if (!Flags.vendorVibrationEffects()) {
Slog.e(TAG, "vibrate; vendor effects feature disabled");
@@ -622,11 +627,6 @@
return null;
}
}
- enforceUpdateAppOpsStatsPermission(uid);
- if (!isEffectValid(effect)) {
- logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_UNSUPPORTED);
- return null;
- }
// Create Vibration.Stats as close to the received request as possible, for tracking.
SingleVibrationSession session = new SingleVibrationSession(token, callerInfo, effect);
HalVibration vib = session.getVibration();
@@ -658,6 +658,7 @@
// If not ignored so far then try to start this vibration.
if (ignoreStatus == null) {
+ // TODO(b/378492007): Investigate if we can move this around AppOpsManager calls
final long ident = Binder.clearCallingIdentity();
try {
if (mCurrentSession != null) {
@@ -703,6 +704,7 @@
if (DEBUG) {
Slog.d(TAG, "Canceling vibration");
}
+ // TODO(b/378492007): Investigate if we can move this around AppOpsManager calls
final long ident = Binder.clearCallingIdentity();
try {
// TODO(b/370948466): investigate why token not checked on external vibrations.
@@ -762,8 +764,18 @@
vibratorIds = new int[0];
}
enforceUpdateAppOpsStatsPermission(uid);
+
+ // Create session with adapter that only uses the session vibrators.
+ SparseArray<VibratorController> sessionVibrators = new SparseArray<>(vibratorIds.length);
+ for (int vibratorId : vibratorIds) {
+ VibratorController controller = mVibrators.get(vibratorId);
+ if (controller != null) {
+ sessionVibrators.put(vibratorId, controller);
+ }
+ }
+ DeviceAdapter deviceAdapter = new DeviceAdapter(mVibrationSettings, sessionVibrators);
VendorVibrationSession session = new VendorVibrationSession(callerInfo, mHandler,
- mVendorVibrationSessionCallbacks, vibratorIds, callback);
+ mVendorVibrationSessionCallbacks, deviceAdapter, callback);
if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
// Force update of user settings before checking if this vibration effect should
@@ -787,12 +799,15 @@
ignoreStatus = Status.IGNORED_UNSUPPORTED;
}
- // Check if any vibrator ID was requested.
- if (ignoreStatus == null && vibratorIds.length == 0) {
- if (DEBUG) {
- Slog.d(TAG, "Empty vibrator ids to start session, ignoring request");
+ // Check if vibrator IDs requested are available.
+ if (ignoreStatus == null) {
+ if (vibratorIds.length == 0
+ || vibratorIds.length != deviceAdapter.getAvailableVibratorIds().length) {
+ Slog.e(TAG, "Bad vibrator ids to start session, ignoring request."
+ + " requested=" + Arrays.toString(vibratorIds)
+ + " available=" + Arrays.toString(mVibratorIds));
+ ignoreStatus = Status.IGNORED_UNSUPPORTED;
}
- ignoreStatus = Status.IGNORED_UNSUPPORTED;
}
// Check if user settings or DnD is set to ignore this session.
@@ -810,6 +825,7 @@
}
if (ignoreStatus == null) {
+ // TODO(b/378492007): Investigate if we can move this around AppOpsManager calls
final long ident = Binder.clearCallingIdentity();
try {
// If not ignored so far then stop ongoing sessions before starting this one.
@@ -839,22 +855,40 @@
private Status startVendorSessionLocked(VendorVibrationSession session) {
Trace.traceBegin(TRACE_TAG_VIBRATOR, "startSessionLocked");
try {
+ long sessionId = session.getSessionId();
+ if (DEBUG) {
+ Slog.d(TAG, "Starting session " + sessionId + " in HAL");
+ }
if (session.isEnded()) {
// Session already ended, possibly cancelled by app cancellation signal.
return session.getStatus();
}
- if (!session.linkToDeath()) {
- return Status.IGNORED_ERROR_TOKEN;
+ int mode = startAppOpModeLocked(session.getCallerInfo());
+ switch (mode) {
+ case AppOpsManager.MODE_ALLOWED:
+ Trace.asyncTraceBegin(TRACE_TAG_VIBRATOR, "vibration", 0);
+ // Make sure mCurrentVibration is set while triggering the HAL.
+ mCurrentSession = session;
+ if (!session.linkToDeath()) {
+ mCurrentSession = null;
+ return Status.IGNORED_ERROR_TOKEN;
+ }
+ if (!mNativeWrapper.startSession(sessionId, session.getVibratorIds())) {
+ Slog.e(TAG, "Error starting session " + sessionId + " on vibrators "
+ + Arrays.toString(session.getVibratorIds()));
+ session.unlinkToDeath();
+ mCurrentSession = null;
+ return Status.IGNORED_UNSUPPORTED;
+ }
+ session.notifyStart();
+ return null;
+ case AppOpsManager.MODE_ERRORED:
+ Slog.w(TAG, "Start AppOpsManager operation errored for uid "
+ + session.getCallerInfo().uid);
+ return Status.IGNORED_ERROR_APP_OPS;
+ default:
+ return Status.IGNORED_APP_OPS;
}
- if (!mNativeWrapper.startSession(session.getSessionId(), session.getVibratorIds())) {
- Slog.e(TAG, "Error starting session " + session.getSessionId()
- + " on vibrators " + Arrays.toString(session.getVibratorIds()));
- session.unlinkToDeath();
- return Status.IGNORED_UNSUPPORTED;
- }
- session.notifyStart();
- mCurrentSession = session;
- return null;
} finally {
Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
@@ -1045,6 +1079,9 @@
@GuardedBy("mLock")
@Nullable
private Status startVibrationOnThreadLocked(SingleVibrationSession session) {
+ if (DEBUG) {
+ Slog.d(TAG, "Starting vibration " + session.getVibration().id + " on thread");
+ }
VibrationStepConductor conductor = createVibrationStepConductor(session.getVibration());
session.setVibrationConductor(conductor);
int mode = startAppOpModeLocked(session.getCallerInfo());
@@ -1080,12 +1117,18 @@
mNextSession = null;
Status errorStatus = startVibrationOnThreadLocked(session);
if (errorStatus != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Error starting next vibration " + session.getVibration().id);
+ }
endSessionLocked(session, errorStatus);
}
} else if (mNextSession instanceof VendorVibrationSession session) {
mNextSession = null;
Status errorStatus = startVendorSessionLocked(session);
if (errorStatus != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Error starting next session " + session.getSessionId());
+ }
endSessionLocked(session, errorStatus);
}
} // External vibrations cannot be started asynchronously.
@@ -1103,6 +1146,16 @@
}
private VibrationStepConductor createVibrationStepConductor(HalVibration vib) {
+ return createVibrationStepConductor(vib, mDeviceAdapter, /* isInSession= */ false);
+ }
+
+ private VibrationStepConductor createSessionVibrationStepConductor(HalVibration vib,
+ DeviceAdapter deviceAdapter) {
+ return createVibrationStepConductor(vib, deviceAdapter, /* isInSession= */ true);
+ }
+
+ private VibrationStepConductor createVibrationStepConductor(HalVibration vib,
+ DeviceAdapter deviceAdapter, boolean isInSession) {
CompletableFuture<Void> requestVibrationParamsFuture = null;
if (Flags.adaptiveHapticsEnabled()
@@ -1114,8 +1167,8 @@
mVibrationSettings.getRequestVibrationParamsTimeoutMs());
}
- return new VibrationStepConductor(vib, /* isInSession= */ false, mVibrationSettings,
- mDeviceAdapter, mVibrationScaler, mFrameworkStatsLogger,
+ return new VibrationStepConductor(vib, isInSession, mVibrationSettings,
+ deviceAdapter, mVibrationScaler, mFrameworkStatsLogger,
requestVibrationParamsFuture, mVibrationThreadCallbacks);
}
@@ -1136,18 +1189,15 @@
private void logAndRecordVibrationAttempt(@Nullable CombinedVibration effect,
CallerInfo callerInfo, Status status) {
- logAndRecordVibration(
- new Vibration.DebugInfoImpl(status, callerInfo,
- VibrationStats.StatsInfo.findVibrationType(effect), new VibrationStats(),
- effect, /* originalEffect= */ null, VibrationScaler.SCALE_NONE,
- VibrationScaler.ADAPTIVE_SCALE_NONE));
+ logAndRecordVibration(createVibrationAttemptDebugInfo(effect, callerInfo, status));
}
private void logAndRecordSessionAttempt(CallerInfo callerInfo, Status status) {
logAndRecordVibration(
new VendorVibrationSession.DebugInfoImpl(status, callerInfo,
SystemClock.uptimeMillis(), System.currentTimeMillis(),
- /* startTime= */ 0, /* endUptime= */ 0, /* endTime= */ 0));
+ /* startTime= */ 0, /* endUptime= */ 0, /* endTime= */ 0,
+ /* vibrations= */ null));
}
private void logAndRecordVibration(DebugInfo info) {
@@ -1156,6 +1206,14 @@
mVibratorManagerRecords.record(info);
}
+ private DebugInfo createVibrationAttemptDebugInfo(@Nullable CombinedVibration effect,
+ CallerInfo callerInfo, Status status) {
+ return new Vibration.DebugInfoImpl(status, callerInfo,
+ VibrationStats.StatsInfo.findVibrationType(effect), new VibrationStats(),
+ effect, /* originalEffect= */ null, VibrationScaler.SCALE_NONE,
+ VibrationScaler.ADAPTIVE_SCALE_NONE);
+ }
+
private void logVibrationStatus(int uid, VibrationAttributes attrs, Status status) {
switch (status) {
case IGNORED_BACKGROUND:
@@ -1766,25 +1824,35 @@
Trace.traceBegin(TRACE_TAG_VIBRATOR, "onVibrationThreadReleased");
try {
synchronized (mLock) {
- if (!(mCurrentSession instanceof SingleVibrationSession session)) {
- if (Build.IS_DEBUGGABLE) {
- Slog.wtf(TAG, "VibrationSession invalid on vibration thread release."
- + " currentSession=" + mCurrentSession);
+ if (mCurrentSession instanceof SingleVibrationSession session) {
+ if (Build.IS_DEBUGGABLE && (session.getVibration().id != vibrationId)) {
+ Slog.wtf(TAG, TextUtils.formatSimple(
+ "VibrationId mismatch on vibration thread release."
+ + " expected=%d, released=%d",
+ session.getVibration().id, vibrationId));
}
- // Only single vibration sessions are ended by thread being released. Abort.
- return;
+ finishAppOpModeLocked(mCurrentSession.getCallerInfo());
+ clearCurrentSessionLocked();
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
+ // Start next vibration if it's waiting for the thread.
+ maybeStartNextSessionLocked();
+ } else if (mCurrentSession instanceof VendorVibrationSession session) {
+ VibrationStepConductor conductor = session.clearVibrationConductor();
+ if (Build.IS_DEBUGGABLE) {
+ if (conductor == null) {
+ Slog.wtf(TAG, "Vendor session without ongoing vibration on"
+ + " thread release. currentSession=" + mCurrentSession);
+ } else if (conductor.getVibration().id != vibrationId) {
+ Slog.wtf(TAG, TextUtils.formatSimple(
+ "VibrationId mismatch on vibration thread release."
+ + " expected=%d, released=%d",
+ conductor.getVibration().id, vibrationId));
+ }
+ }
+ } else if (Build.IS_DEBUGGABLE) {
+ Slog.wtf(TAG, "VibrationSession invalid on vibration thread release."
+ + " currentSession=" + mCurrentSession);
}
- if (Build.IS_DEBUGGABLE && (session.getVibration().id != vibrationId)) {
- Slog.wtf(TAG, TextUtils.formatSimple(
- "VibrationId mismatch on vibration thread release."
- + " expected=%d, released=%d",
- session.getVibration().id, vibrationId));
- }
- finishAppOpModeLocked(mCurrentSession.getCallerInfo());
- clearCurrentSessionLocked();
- Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
- // Start next vibration if it's waiting for the thread.
- maybeStartNextSessionLocked();
}
} finally {
Trace.traceEnd(TRACE_TAG_VIBRATOR);
@@ -1839,6 +1907,86 @@
implements VendorVibrationSession.VibratorManagerHooks {
@Override
+ public void vibrate(long sessionId, CallerInfo callerInfo, CombinedVibration effect) {
+ if (DEBUG) {
+ Slog.d(TAG, "Vibration session " + sessionId + " vibration requested");
+ }
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "sessionVibrate");
+ try {
+ synchronized (mLock) {
+ if (!(mCurrentSession instanceof VendorVibrationSession session)) {
+ if (Build.IS_DEBUGGABLE) {
+ Slog.wtf(TAG, "VibrationSession invalid on session vibrate."
+ + " currentSession=" + mCurrentSession);
+ }
+ // Only vendor vibration sessions can handle this call. Abort.
+ return;
+ }
+ if (session.getSessionId() != sessionId) {
+ if (Build.IS_DEBUGGABLE) {
+ Slog.wtf(TAG, TextUtils.formatSimple(
+ "SessionId mismatch on vendor vibration session vibrate."
+ + " expected=%d, released=%d",
+ session.getSessionId(), sessionId));
+ }
+ // Only the ongoing vendor vibration sessions can handle this call. Abort.
+ return;
+ }
+ if (session.wasEndRequested()) {
+ if (DEBUG) {
+ Slog.d(TAG, "session vibrate; session is ending, vibration ignored");
+ }
+ session.notifyVibrationAttempt(createVibrationAttemptDebugInfo(effect,
+ callerInfo, Status.IGNORED_ERROR_SCHEDULING));
+ return;
+ }
+ if (!isEffectValid(effect)) {
+ session.notifyVibrationAttempt(createVibrationAttemptDebugInfo(effect,
+ callerInfo, Status.IGNORED_UNSUPPORTED));
+ return;
+ }
+ if (effect.getDuration() == Long.MAX_VALUE) {
+ // Repeating effects cannot be played by the service in a session.
+ session.notifyVibrationAttempt(createVibrationAttemptDebugInfo(effect,
+ callerInfo, Status.IGNORED_UNSUPPORTED));
+ return;
+ }
+ // Create Vibration.Stats as close to the request as possible, for tracking.
+ HalVibration vib = new HalVibration(callerInfo, effect);
+ vib.fillFallbacks(mVibrationSettings::getFallbackEffect);
+
+ if (callerInfo.attrs.isFlagSet(
+ VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
+ // Force update of user settings before checking if this vibration effect
+ // should be ignored or scaled.
+ mVibrationSettings.update();
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "Starting vibrate for vibration " + vib.id
+ + " in session " + sessionId);
+ }
+
+ VibrationStepConductor conductor =
+ createSessionVibrationStepConductor(vib, session.getDeviceAdapter());
+ if (session.maybeSetVibrationConductor(conductor)) {
+ if (!mVibrationThread.runVibrationOnVibrationThread(conductor)) {
+ // Shouldn't happen. The method call already logs.
+ vib.end(new Vibration.EndInfo(Status.IGNORED_ERROR_SCHEDULING));
+ session.clearVibrationConductor(); // Rejected by thread, clear it.
+ }
+ } else {
+ // Cannot set vibration in session, log failed attempt.
+ session.notifyVibrationAttempt(createVibrationAttemptDebugInfo(effect,
+ callerInfo, Status.IGNORED_ERROR_SCHEDULING));
+ }
+ }
+ } finally {
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ @Override
public void endSession(long sessionId, boolean shouldAbort) {
if (DEBUG) {
Slog.d(TAG, "Vibration session " + sessionId
@@ -1874,6 +2022,12 @@
+ " expected=%d, released=%d",
session.getSessionId(), sessionId));
}
+ // Make sure all controllers in session are reset after session ended.
+ // This will update the vibrator state to isVibrating = false for listeners.
+ for (int vibratorId : session.getVibratorIds()) {
+ mVibrators.get(vibratorId).off();
+ }
+ finishAppOpModeLocked(mCurrentSession.getCallerInfo());
clearCurrentSessionLocked();
// Start next vibration if it's waiting for the HAL session to be over.
maybeStartNextSessionLocked();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 54b257c..8268cae 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1022,12 +1022,12 @@
return;
}
- final boolean disableSecureWindows;
+ boolean disableSecureWindows;
try {
disableSecureWindows = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.DISABLE_SECURE_WINDOWS, 0) != 0;
} catch (Settings.SettingNotFoundException e) {
- return;
+ disableSecureWindows = false;
}
if (mDisableSecureWindows == disableSecureWindows) {
return;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ad7e21c..6292cbf 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -505,6 +505,7 @@
import com.android.internal.app.LocalePicker;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.net.NetworkUtilsInternal;
import com.android.internal.notification.SystemNotificationChannels;
@@ -716,24 +717,24 @@
SECURE_SETTINGS_DEVICEOWNER_ALLOWLIST.add(Settings.Secure.LOCATION_MODE);
GLOBAL_SETTINGS_ALLOWLIST = new ArraySet<>();
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.ADB_ENABLED);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.ADB_WIFI_ENABLED);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.AUTO_TIME);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.AUTO_TIME_ZONE);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.DATA_ROAMING);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.USB_MASS_STORAGE_ENABLED);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.WIFI_SLEEP_POLICY);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.STAY_ON_WHILE_PLUGGED_IN);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.PRIVATE_DNS_MODE);
- GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.PRIVATE_DNS_SPECIFIER);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.ADB_ENABLED);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.ADB_WIFI_ENABLED);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.AUTO_TIME);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.AUTO_TIME_ZONE);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.DATA_ROAMING);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.USB_MASS_STORAGE_ENABLED);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.WIFI_SLEEP_POLICY);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.STAY_ON_WHILE_PLUGGED_IN);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Global.PRIVATE_DNS_MODE);
+ GLOBAL_SETTINGS_ALLOWLIST.add(PRIVATE_DNS_SPECIFIER);
GLOBAL_SETTINGS_DEPRECATED = new ArraySet<>();
- GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.BLUETOOTH_ON);
- GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.DEVELOPMENT_SETTINGS_ENABLED);
- GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.MODE_RINGER);
- GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.NETWORK_PREFERENCE);
- GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.WIFI_ON);
+ GLOBAL_SETTINGS_DEPRECATED.add(Global.BLUETOOTH_ON);
+ GLOBAL_SETTINGS_DEPRECATED.add(Global.DEVELOPMENT_SETTINGS_ENABLED);
+ GLOBAL_SETTINGS_DEPRECATED.add(Global.MODE_RINGER);
+ GLOBAL_SETTINGS_DEPRECATED.add(Global.NETWORK_PREFERENCE);
+ GLOBAL_SETTINGS_DEPRECATED.add(Global.WIFI_ON);
SYSTEM_SETTINGS_ALLOWLIST = new ArraySet<>();
SYSTEM_SETTINGS_ALLOWLIST.add(Settings.System.SCREEN_BRIGHTNESS);
@@ -776,7 +777,7 @@
/**
* Strings logged with {@link
- * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB},
+ * MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB},
* {@link DevicePolicyEnums#PROVISIONING_ENTRY_POINT_ADB},
* {@link DevicePolicyEnums#SET_NETWORK_LOGGING_ENABLED} and
* {@link DevicePolicyEnums#RETRIEVE_NETWORK_LOGS}.
@@ -787,11 +788,11 @@
/**
* For admin apps targeting R+, throw when the app sets password requirement
* that is not taken into account at given quality. For example when quality is set
- * to {@link android.app.admin.DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}, it doesn't
+ * to {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}, it doesn't
* make sense to require certain password length. If the intent is to require a password of
* certain length having at least NUMERIC quality, the admin should first call
- * {@link android.app.admin.DevicePolicyManager#setPasswordQuality} and only then call
- * {@link android.app.admin.DevicePolicyManager#setPasswordMinimumLength}.
+ * {@link DevicePolicyManager#setPasswordQuality} and only then call
+ * {@link DevicePolicyManager#setPasswordMinimumLength}.
*
* <p>Conversely when an admin app targeting R+ lowers password quality, those
* requirements that stop making sense are reset to default values.
@@ -802,9 +803,9 @@
/**
* Admin apps targeting Android R+ may not use
- * {@link android.app.admin.DevicePolicyManager#setSecureSetting} to change the deprecated
- * {@link android.provider.Settings.Secure#LOCATION_MODE} setting. Instead they should use
- * {@link android.app.admin.DevicePolicyManager#setLocationEnabled}.
+ * {@link DevicePolicyManager#setSecureSetting} to change the deprecated
+ * {@link Settings.Secure#LOCATION_MODE} setting. Instead they should use
+ * {@link DevicePolicyManager#setLocationEnabled}.
*/
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
@@ -850,7 +851,7 @@
private @interface CopyAccountStatus {}
/**
- * Mapping of {@link android.app.admin.DevicePolicyManager.ApplicationExemptionConstants} to
+ * Mapping of {@link DevicePolicyManager.ApplicationExemptionConstants} to
* corresponding app-ops.
*/
private static final Map<Integer, String> APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS =
@@ -882,11 +883,11 @@
/**
* Admin apps targeting Android S+ may not use
- * {@link android.app.admin.DevicePolicyManager#setPasswordQuality} to set password quality
+ * {@link DevicePolicyManager#setPasswordQuality} to set password quality
* on the {@code DevicePolicyManager} instance obtained by calling
- * {@link android.app.admin.DevicePolicyManager#getParentProfileInstance}.
+ * {@link DevicePolicyManager#getParentProfileInstance}.
* Instead, they should use
- * {@link android.app.admin.DevicePolicyManager#setRequiredPasswordComplexity} to set
+ * {@link DevicePolicyManager#setRequiredPasswordComplexity} to set
* coarse-grained password requirements device-wide.
*/
@ChangeId
@@ -895,7 +896,7 @@
/**
* For Admin Apps targeting U+
- * If {@link android.security.IKeyChainService#setGrant} is called with an alias with no
+ * If {@link IKeyChainService#setGrant} is called with an alias with no
* existing key, throw IllegalArgumentException.
*/
@ChangeId
@@ -1477,8 +1478,8 @@
if (packageName == null || packageName.equals(adminPackage)) {
if (mIPackageManager.getPackageInfo(adminPackage, 0, userHandle) == null
|| mIPackageManager.getReceiverInfo(aa.info.getComponent(),
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ MATCH_DIRECT_BOOT_AWARE
+ | MATCH_DIRECT_BOOT_UNAWARE,
userHandle) == null) {
Slogf.e(LOG_TAG, String.format(
"Admin package %s not found for user %d, removing active admin",
@@ -1696,7 +1697,7 @@
return getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
}
- Context createContextAsUser(UserHandle user) throws PackageManager.NameNotFoundException {
+ Context createContextAsUser(UserHandle user) throws NameNotFoundException {
final String packageName = mContext.getPackageName();
return mContext.createPackageContextAsUser(packageName, 0, user);
}
@@ -2008,25 +2009,25 @@
}
void settingsGlobalPutStringForUser(String name, String value, int userHandle) {
- Settings.Global.putStringForUser(mContext.getContentResolver(),
+ Global.putStringForUser(mContext.getContentResolver(),
name, value, userHandle);
}
int settingsGlobalGetInt(String name, int def) {
- return Settings.Global.getInt(mContext.getContentResolver(), name, def);
+ return Global.getInt(mContext.getContentResolver(), name, def);
}
@Nullable
String settingsGlobalGetString(String name) {
- return Settings.Global.getString(mContext.getContentResolver(), name);
+ return Global.getString(mContext.getContentResolver(), name);
}
void settingsGlobalPutInt(String name, int value) {
- Settings.Global.putInt(mContext.getContentResolver(), name, value);
+ Global.putInt(mContext.getContentResolver(), name, value);
}
void settingsGlobalPutString(String name, String value) {
- Settings.Global.putString(mContext.getContentResolver(), name, value);
+ Global.putString(mContext.getContentResolver(), name, value);
}
void settingsSystemPutStringForUser(String name, String value, int userId) {
@@ -3206,8 +3207,8 @@
return mIPackageManager.getReceiverInfo(adminName,
GET_META_DATA
| PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
- | PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle);
+ | MATCH_DIRECT_BOOT_AWARE
+ | MATCH_DIRECT_BOOT_UNAWARE, userHandle);
} catch (RemoteException e) {
// shouldn't happen.
Slogf.wtf(LOG_TAG, "Error getting receiver info", e);
@@ -3218,9 +3219,9 @@
throw new IllegalArgumentException("Unknown admin: " + adminName);
}
- if (!permission.BIND_DEVICE_ADMIN.equals(ai.permission)) {
+ if (!BIND_DEVICE_ADMIN.equals(ai.permission)) {
final String message = "DeviceAdminReceiver " + adminName + " must be protected with "
- + permission.BIND_DEVICE_ADMIN;
+ + BIND_DEVICE_ADMIN;
Slogf.w(LOG_TAG, message);
if (throwForMissingPermission &&
ai.applicationInfo.targetSdkVersion > Build.VERSION_CODES.M) {
@@ -4412,8 +4413,8 @@
final ApplicationInfo ai;
try {
ai = mInjector.getIPackageManager().getApplicationInfo(packageName,
- (PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE), userHandle);
+ (MATCH_DIRECT_BOOT_AWARE
+ | MATCH_DIRECT_BOOT_UNAWARE), userHandle);
} catch (RemoteException e) {
throw new IllegalStateException(e);
}
@@ -5992,7 +5993,7 @@
Preconditions.checkCallAuthorization(admin != null,
"Unauthorized caller cannot call resetPassword.");
if (getTargetSdk(admin.info.getPackageName(),
- userHandle) <= android.os.Build.VERSION_CODES.M) {
+ userHandle) <= Build.VERSION_CODES.M) {
Slogf.e(LOG_TAG, "Device admin can no longer call resetPassword()");
return false;
}
@@ -6142,7 +6143,7 @@
if (policy.mLastMaximumTimeToLock != Long.MAX_VALUE) {
// Make sure KEEP_SCREEN_ON is disabled, since that
// would allow bypassing of the maximum time to lock.
- mInjector.settingsGlobalPutInt(Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
+ mInjector.settingsGlobalPutInt(Global.STAY_ON_WHILE_PLUGGED_IN, 0);
}
getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin(parentId, timeMs);
});
@@ -6333,7 +6334,7 @@
} else {
ActiveAdmin admin = getActiveAdminOrCheckPermissionForCallerLocked(
null,
- DeviceAdminInfo.USES_POLICY_FORCE_LOCK,
+ USES_POLICY_FORCE_LOCK,
parent,
LOCK_DEVICE);
adminComponent = admin == null ? null : admin.info.getComponent();
@@ -7475,7 +7476,7 @@
* privileged APIs.
* <p>
* This is done by checking that the calling package is authorized to perform the app operation
- * {@link android.app.AppOpsManager#OP_MANAGE_CREDENTIALS}.
+ * {@link AppOpsManager#OP_MANAGE_CREDENTIALS}.
*
* @param caller the calling identity
* @return {@code true} if the calling process is the credential management app.
@@ -7485,7 +7486,7 @@
AppOpsManager appOpsManager = mInjector.getAppOpsManager();
if (appOpsManager == null) return false;
return appOpsManager.noteOpNoThrow(AppOpsManager.OP_MANAGE_CREDENTIALS, caller.getUid(),
- caller.getPackageName(), null, null) == AppOpsManager.MODE_ALLOWED;
+ caller.getPackageName(), null, null) == MODE_ALLOWED;
});
}
@@ -7796,7 +7797,7 @@
public void wipeDataWithReason(String callerPackageName, int flags,
@NonNull String wipeReasonForUser, boolean calledOnParentInstance,
boolean factoryReset) {
- if (!mHasFeature && !hasCallingOrSelfPermission(permission.MASTER_CLEAR)) {
+ if (!mHasFeature && !hasCallingOrSelfPermission(MASTER_CLEAR)) {
return;
}
CallerIdentity caller = getCallerIdentity(callerPackageName);
@@ -8194,7 +8195,7 @@
synchronized (getLockObject()) {
if (who == null) {
Preconditions.checkCallAuthorization(frpManagementAgentUid == caller.getUid()
- || hasCallingPermission(permission.MASTER_CLEAR)
+ || hasCallingPermission(MASTER_CLEAR)
|| hasCallingPermission(MANAGE_DEVICE_POLICY_FACTORY_RESET),
"Must be called by the FRP management agent on device");
admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
@@ -8681,9 +8682,9 @@
Slogf.e(LOG_TAG, "Invalid proxy properties, ignoring: " + proxyProperties.toString());
return;
}
- mInjector.settingsGlobalPutString(Settings.Global.GLOBAL_HTTP_PROXY_HOST, data[0]);
- mInjector.settingsGlobalPutInt(Settings.Global.GLOBAL_HTTP_PROXY_PORT, proxyPort);
- mInjector.settingsGlobalPutString(Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
+ mInjector.settingsGlobalPutString(Global.GLOBAL_HTTP_PROXY_HOST, data[0]);
+ mInjector.settingsGlobalPutInt(Global.GLOBAL_HTTP_PROXY_PORT, proxyPort);
+ mInjector.settingsGlobalPutString(Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
exclusionList);
}
@@ -8804,7 +8805,7 @@
}
final int rawStatus = getEncryptionStatus();
- if ((rawStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER) && legacyApp) {
+ if ((rawStatus == ENCRYPTION_STATUS_ACTIVE_PER_USER) && legacyApp) {
return DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE;
}
return rawStatus;
@@ -8828,7 +8829,7 @@
*/
private int getEncryptionStatus() {
if (mInjector.storageManagerIsFileBasedEncryptionEnabled()) {
- return DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER;
+ return ENCRYPTION_STATUS_ACTIVE_PER_USER;
} else {
return DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED;
}
@@ -9023,7 +9024,7 @@
// Turn AUTO_TIME on in settings if it is required
if (required) {
mInjector.binderWithCleanCallingIdentity(
- () -> mInjector.settingsGlobalPutInt(Settings.Global.AUTO_TIME,
+ () -> mInjector.settingsGlobalPutInt(Global.AUTO_TIME,
1 /* AUTO_TIME on */));
}
DevicePolicyEventLogger
@@ -10455,7 +10456,7 @@
policy.mDelegationMap.clear();
policy.mStatusBarDisabled = false;
policy.mSecondaryLockscreenEnabled = false;
- policy.mUserProvisioningState = DevicePolicyManager.STATE_USER_UNMANAGED;
+ policy.mUserProvisioningState = STATE_USER_UNMANAGED;
policy.mAffiliationIds.clear();
resetAffiliationCacheLocked();
policy.mLockTaskPackages.clear();
@@ -10490,7 +10491,7 @@
@Override
public int getUserProvisioningState(int userHandle) {
if (!mHasFeature) {
- return DevicePolicyManager.STATE_USER_UNMANAGED;
+ return STATE_USER_UNMANAGED;
}
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(canManageUsers(caller)
@@ -10545,7 +10546,7 @@
// ADB shell can only move directly from un-managed to finalized as part of
// directly setting profile-owner or device-owner.
if (getUserProvisioningState(userId)
- != DevicePolicyManager.STATE_USER_UNMANAGED
+ != STATE_USER_UNMANAGED
|| newState != STATE_USER_SETUP_FINALIZED) {
throw new IllegalStateException("Not allowed to change provisioning state "
+ "unless current provisioning state is unmanaged, and new state"
@@ -10583,9 +10584,9 @@
}
// Valid transitions for normal use-cases.
switch (currentState) {
- case DevicePolicyManager.STATE_USER_UNMANAGED:
+ case STATE_USER_UNMANAGED:
// Can move to any state from unmanaged (except itself as an edge case)..
- if (newState != DevicePolicyManager.STATE_USER_UNMANAGED) {
+ if (newState != STATE_USER_UNMANAGED) {
return;
}
break;
@@ -10609,7 +10610,7 @@
break;
case DevicePolicyManager.STATE_USER_PROFILE_FINALIZED:
// Should only move to an unmanaged state after removing the work profile.
- if (newState == DevicePolicyManager.STATE_USER_UNMANAGED) {
+ if (newState == STATE_USER_UNMANAGED) {
return;
}
break;
@@ -10981,7 +10982,7 @@
UserHandle userHandle = UserHandle.of(userId);
userContext = mContext.createPackageContextAsUser(packageName, /* flags= */ 0,
userHandle);
- } catch (PackageManager.NameNotFoundException nnfe) {
+ } catch (NameNotFoundException nnfe) {
Slogf.w(LOG_TAG, nnfe, "%s is not installed for user %d", packageName, userId);
return null;
}
@@ -11201,20 +11202,20 @@
}
private boolean canQueryAdminPolicy(CallerIdentity caller) {
- return hasCallingOrSelfPermission(permission.QUERY_ADMIN_POLICY);
+ return hasCallingOrSelfPermission(QUERY_ADMIN_POLICY);
}
private boolean hasPermission(String permission, int pid, int uid) {
- return mContext.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED;
+ return mContext.checkPermission(permission, pid, uid) == PERMISSION_GRANTED;
}
private boolean hasCallingPermission(String permission) {
- return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED;
+ return mContext.checkCallingPermission(permission) == PERMISSION_GRANTED;
}
private boolean hasCallingOrSelfPermission(String permission) {
return mContext.checkCallingOrSelfPermission(permission)
- == PackageManager.PERMISSION_GRANTED;
+ == PERMISSION_GRANTED;
}
private boolean hasPermissionForPreflight(CallerIdentity caller, String permission) {
@@ -11520,7 +11521,7 @@
private String getEncryptionStatusName(int encryptionStatus) {
switch (encryptionStatus) {
- case DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER:
+ case ENCRYPTION_STATUS_ACTIVE_PER_USER:
return "per-user";
case DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED:
return "unsupported";
@@ -12602,7 +12603,7 @@
if ((flags & DevicePolicyManager.SKIP_SETUP_WIZARD) != 0) {
Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.USER_SETUP_COMPLETE, 1, userHandle);
+ USER_SETUP_COMPLETE, 1, userHandle);
}
sendProvisioningCompletedBroadcast(
@@ -14018,8 +14019,8 @@
List<ResolveInfo> activitiesToEnable = mIPackageManager
.queryIntentActivities(intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ MATCH_DIRECT_BOOT_AWARE
+ | MATCH_DIRECT_BOOT_UNAWARE,
parentUserId)
.getList();
@@ -14906,7 +14907,7 @@
if (policy == null) {
// We default on the power button menu, in order to be consistent with pre-P
// behaviour.
- return DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+ return LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
}
return policy.getFlags();
}
@@ -15035,7 +15036,7 @@
"Permission denial: device owners cannot update %1$s", setting));
}
- if (Settings.Global.STAY_ON_WHILE_PLUGGED_IN.equals(setting)) {
+ if (Global.STAY_ON_WHILE_PLUGGED_IN.equals(setting)) {
// ignore if it contradicts an existing policy
long timeMs = getMaximumTimeToLock(
who, mInjector.userHandleGetCallingUserId(), /* parent */ false);
@@ -15540,7 +15541,7 @@
final int N = users.size();
for (int i = 0; i < N; i++) {
int userHandle = users.get(i).id;
- if (mInjector.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0,
+ if (mInjector.settingsSecureGetIntForUser(USER_SETUP_COMPLETE, 0,
userHandle) != 0) {
DevicePolicyData policy = getUserData(userHandle);
if (!policy.mUserSetupComplete) {
@@ -15568,7 +15569,7 @@
private class SetupContentObserver extends ContentObserver {
private final Uri mUserSetupComplete = Settings.Secure.getUriFor(
- Settings.Secure.USER_SETUP_COMPLETE);
+ USER_SETUP_COMPLETE);
private final Uri mPaired = Settings.Secure.getUriFor(Settings.Secure.DEVICE_PAIRED);
private final Uri mDefaultImeChanged = Settings.Secure.getUriFor(
Settings.Secure.DEFAULT_INPUT_METHOD);
@@ -15616,7 +15617,7 @@
private class DevicePolicyConstantsObserver extends ContentObserver {
final Uri mConstantsUri =
- Settings.Global.getUriFor(Settings.Global.DEVICE_POLICY_CONSTANTS);
+ Global.getUriFor(Global.DEVICE_POLICY_CONSTANTS);
DevicePolicyConstantsObserver(Handler handler) {
super(handler);
@@ -15909,9 +15910,9 @@
final int uid = Objects.requireNonNull(
mInjector.getPackageManager().getApplicationInfoAsUser(
Objects.requireNonNull(packageName), /* flags= */ 0, userId)).uid;
- return PackageManager.PERMISSION_GRANTED
+ return PERMISSION_GRANTED
== ActivityManager.checkComponentPermission(
- android.Manifest.permission.MODIFY_QUIET_MODE, uid, /* owningUid= */
+ permission.MODIFY_QUIET_MODE, uid, /* owningUid= */
-1, /* exported= */ true);
} catch (NameNotFoundException ex) {
Slogf.w(LOG_TAG, "Cannot find the package %s to check for permissions.",
@@ -16048,7 +16049,7 @@
private @Mode int findInteractAcrossProfilesResetMode(String packageName) {
return getDefaultCrossProfilePackages().contains(packageName)
- ? AppOpsManager.MODE_ALLOWED
+ ? MODE_ALLOWED
: AppOpsManager.opToDefaultMode(AppOpsManager.OP_INTERACT_ACROSS_PROFILES);
}
@@ -16774,13 +16775,13 @@
synchronized (getLockObject()) {
long ident = mInjector.binderClearCallingIdentity();
boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId())
- >= android.os.Build.VERSION_CODES.Q;
+ >= Build.VERSION_CODES.Q;
try {
if (!isPostQAdmin) {
// Legacy admins assume that they cannot control pre-M apps
if (getTargetSdk(packageName, caller.getUserId())
- < android.os.Build.VERSION_CODES.M) {
+ < Build.VERSION_CODES.M) {
callback.sendResult(null);
return;
}
@@ -16791,7 +16792,7 @@
}
if (grantState == PERMISSION_GRANT_STATE_GRANTED
|| grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
- || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
+ || grantState == PERMISSION_GRANT_STATE_DEFAULT) {
AdminPermissionControlParams permissionParams =
new AdminPermissionControlParams(packageName, permission,
grantState,
@@ -16826,26 +16827,26 @@
private static final List<String> SENSOR_PERMISSIONS = new ArrayList<>();
{
- SENSOR_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION);
- SENSOR_PERMISSIONS.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
- SENSOR_PERMISSIONS.add(Manifest.permission.ACCESS_COARSE_LOCATION);
- SENSOR_PERMISSIONS.add(Manifest.permission.CAMERA);
- SENSOR_PERMISSIONS.add(Manifest.permission.RECORD_AUDIO);
- SENSOR_PERMISSIONS.add(Manifest.permission.ACTIVITY_RECOGNITION);
- SENSOR_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
- SENSOR_PERMISSIONS.add(Manifest.permission.BACKGROUND_CAMERA);
- SENSOR_PERMISSIONS.add(Manifest.permission.RECORD_BACKGROUND_AUDIO);
- SENSOR_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
+ SENSOR_PERMISSIONS.add(permission.ACCESS_FINE_LOCATION);
+ SENSOR_PERMISSIONS.add(permission.ACCESS_BACKGROUND_LOCATION);
+ SENSOR_PERMISSIONS.add(permission.ACCESS_COARSE_LOCATION);
+ SENSOR_PERMISSIONS.add(permission.CAMERA);
+ SENSOR_PERMISSIONS.add(permission.RECORD_AUDIO);
+ SENSOR_PERMISSIONS.add(permission.ACTIVITY_RECOGNITION);
+ SENSOR_PERMISSIONS.add(permission.BODY_SENSORS);
+ SENSOR_PERMISSIONS.add(permission.BACKGROUND_CAMERA);
+ SENSOR_PERMISSIONS.add(permission.RECORD_BACKGROUND_AUDIO);
+ SENSOR_PERMISSIONS.add(permission.BODY_SENSORS_BACKGROUND);
}
private boolean canGrantPermission(CallerIdentity caller, String permission,
String targetPackageName) {
boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId())
- >= android.os.Build.VERSION_CODES.Q;
+ >= Build.VERSION_CODES.Q;
if (!isPostQAdmin) {
// Legacy admins assume that they cannot control pre-M apps
if (getTargetSdk(targetPackageName, caller.getUserId())
- < android.os.Build.VERSION_CODES.M) {
+ < Build.VERSION_CODES.M) {
return false;
}
}
@@ -16892,7 +16893,7 @@
throws RemoteException {
int granted;
if (getTargetSdk(caller.getPackageName(), caller.getUserId())
- < android.os.Build.VERSION_CODES.Q) {
+ < Build.VERSION_CODES.Q) {
// The per-Q behavior was to not check the app-ops state.
granted = mIPackageManager.checkPermission(permission, packageName, userId);
} else {
@@ -16901,11 +16902,11 @@
if (packageState == null) {
Slog.w(LOG_TAG, "Can't get permission state for missing package "
+ packageName);
- return DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+ return PERMISSION_GRANT_STATE_DEFAULT;
} else if (!packageState.getUserStateOrDefault(userId).isInstalled()) {
Slog.w(LOG_TAG, "Can't get permission state for uninstalled package "
+ packageName);
- return DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+ return PERMISSION_GRANT_STATE_DEFAULT;
} else {
if (PermissionChecker.checkPermissionForPreflight(mContext, permission,
PermissionChecker.PID_UNKNOWN,
@@ -16913,7 +16914,7 @@
!= PermissionChecker.PERMISSION_GRANTED) {
granted = PackageManager.PERMISSION_DENIED;
} else {
- granted = PackageManager.PERMISSION_GRANTED;
+ granted = PERMISSION_GRANTED;
}
}
@@ -16924,11 +16925,11 @@
if ((permFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED)
!= PackageManager.FLAG_PERMISSION_POLICY_FIXED) {
// Not controlled by policy
- return DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+ return PERMISSION_GRANT_STATE_DEFAULT;
} else {
// Policy controlled so return result based on permission grant state
- return granted == PackageManager.PERMISSION_GRANTED
- ? DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
+ return granted == PERMISSION_GRANTED
+ ? PERMISSION_GRANT_STATE_GRANTED
: DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
}
}
@@ -17048,9 +17049,9 @@
}
if (action != null) {
switch (action) {
- case DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE:
+ case ACTION_PROVISION_MANAGED_PROFILE:
return checkManagedProfileProvisioningPreCondition(packageName, userId);
- case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE:
+ case ACTION_PROVISION_MANAGED_DEVICE:
case DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE:
return checkDeviceOwnerProvisioningPreCondition(componentName, userId);
}
@@ -18397,7 +18398,7 @@
hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
boolean isUserCompleted = mInjector.settingsSecureGetIntForUser(
- Settings.Secure.USER_SETUP_COMPLETE, 0, userId) != 0;
+ USER_SETUP_COMPLETE, 0, userId) != 0;
DevicePolicyData policy = getUserData(userId);
policy.mUserSetupComplete = isUserCompleted;
mStateCache.setDeviceProvisioned(isUserCompleted);
@@ -20028,7 +20029,7 @@
}
private boolean isDeviceAB() {
- return "true".equalsIgnoreCase(android.os.SystemProperties
+ return "true".equalsIgnoreCase(SystemProperties
.get(AB_DEVICE_KEY, ""));
}
@@ -20295,7 +20296,7 @@
return mOwners.hasDeviceOwner()
&& mInjector.getIActivityManager().getLockTaskModeState()
== ActivityManager.LOCK_TASK_MODE_LOCKED
- && !isLockTaskFeatureEnabled(DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO)
+ && !isLockTaskFeatureEnabled(LOCK_TASK_FEATURE_SYSTEM_INFO)
&& !deviceHasKeyguard()
&& !inEphemeralUserSession();
}
@@ -20306,7 +20307,7 @@
int lockTaskFeatures = policy == null
// We default on the power button menu, in order to be consistent with pre-P
// behaviour.
- ? DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS
+ ? LOCK_TASK_FEATURE_GLOBAL_ACTIONS
: policy.getFlags();
return (lockTaskFeatures & lockTaskFeature) == lockTaskFeature;
}
@@ -21052,7 +21053,7 @@
private boolean canHandleCheckPolicyComplianceIntent(CallerIdentity caller) {
mInjector.binderWithCleanCallingIdentity(() -> {
- final Intent intent = new Intent(DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE);
+ final Intent intent = new Intent(ACTION_CHECK_POLICY_COMPLIANCE);
intent.setPackage(caller.getPackageName());
final List<ResolveInfo> handlers =
mInjector.getPackageManager().queryIntentActivitiesAsUser(intent, /* flags= */
@@ -21261,6 +21262,17 @@
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
+ if (Flags.splitCreateManagedProfileEnabled()) {
+ return mInjector.binderWithCleanCallingIdentity(() -> {
+ UserHandle managedProfileUser =
+ createManagedProfileInternal(provisioningParams, caller);
+ maybeMigrateAccount(managedProfileUser.getIdentifier(), caller.getUserId(),
+ provisioningParams.getAccountToMigrate(),
+ provisioningParams.isKeepingAccountOnMigration(), callerPackage);
+ finalizeCreateManagedProfileInternal(provisioningParams, managedProfileUser);
+ return managedProfileUser;
+ });
+ }
provisioningParams.logParams(callerPackage);
UserInfo userInfo = null;
@@ -21354,6 +21366,130 @@
}
@Override
+ public UserHandle createManagedProfile(
+ @NonNull ManagedProfileProvisioningParams provisioningParams,
+ @NonNull String callerPackage) {
+ Objects.requireNonNull(provisioningParams, "provisioningParams is null");
+ Objects.requireNonNull(callerPackage, "callerPackage is null");
+ Objects.requireNonNull(provisioningParams.getProfileAdminComponentName(), "admin is null");
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
+ CallerIdentity caller = getCallerIdentity(callerPackage);
+
+ return mInjector.binderWithCleanCallingIdentity(() ->
+ createManagedProfileInternal(provisioningParams, caller));
+ }
+
+ private UserHandle createManagedProfileInternal(
+ @NonNull ManagedProfileProvisioningParams provisioningParams,
+ @NonNull CallerIdentity caller) {
+ provisioningParams.logParams(caller.getPackageName());
+ final ComponentName admin = provisioningParams.getProfileAdminComponentName();
+ final int callingUserId = caller.getUserId();
+ UserInfo userInfo = null;
+ try {
+ final int result = checkProvisioningPreconditionSkipPermission(
+ ACTION_PROVISION_MANAGED_PROFILE, admin, callingUserId);
+ if (result != STATUS_OK) {
+ throw new ServiceSpecificException(
+ ERROR_PRE_CONDITION_FAILED,
+ "Provisioning preconditions failed with result: " + result);
+ }
+
+ final long startTime = SystemClock.elapsedRealtime();
+
+ onCreateAndProvisionManagedProfileStarted(provisioningParams);
+
+ userInfo = createProfileForUser(provisioningParams, callingUserId);
+ if (userInfo == null) {
+ throw new ServiceSpecificException(
+ ERROR_PROFILE_CREATION_FAILED,
+ "Error creating profile, createProfileForUserEvenWhenDisallowed "
+ + "returned null.");
+ }
+ resetInteractAcrossProfilesAppOps(caller.getUserId());
+ logEventDuration(
+ DevicePolicyEnums.PLATFORM_PROVISIONING_CREATE_PROFILE_MS,
+ startTime,
+ caller.getPackageName());
+
+ maybeInstallDevicePolicyManagementRoleHolderInUser(userInfo.id);
+ installExistingAdminPackage(userInfo.id, admin.getPackageName());
+
+ if (!enableAdminAndSetProfileOwner(userInfo.id, caller.getUserId(), admin)) {
+ throw new ServiceSpecificException(
+ ERROR_SETTING_PROFILE_OWNER_FAILED,
+ "Error setting profile owner.");
+ }
+ setUserSetupComplete(userInfo.id);
+ startProfileForSetup(userInfo.id, caller.getPackageName());
+
+ if (provisioningParams.isOrganizationOwnedProvisioning()) {
+ synchronized (getLockObject()) {
+ setProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(admin, userInfo.id,
+ true);
+ }
+ }
+ return userInfo.getUserHandle();
+ } catch (Exception e) {
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.PLATFORM_PROVISIONING_ERROR)
+ .setStrings(caller.getPackageName())
+ .write();
+ // In case of any errors during provisioning, remove the newly created profile.
+ if (userInfo != null) {
+ mUserManager.removeUserEvenWhenDisallowed(userInfo.id);
+ }
+ throw e;
+ }
+ }
+
+ private UserInfo createProfileForUser(ManagedProfileProvisioningParams params, int userId) {
+ final Set<String> nonRequiredApps = params.isLeaveAllSystemAppsEnabled()
+ ? Collections.emptySet()
+ : mOverlayPackagesProvider.getNonRequiredApps(params.getProfileAdminComponentName(),
+ userId, ACTION_PROVISION_MANAGED_PROFILE);
+ if (nonRequiredApps.isEmpty()) {
+ Slogf.i(LOG_TAG, "No disallowed packages for the managed profile.");
+ } else {
+ for (String packageName : nonRequiredApps) {
+ Slogf.i(LOG_TAG, "Disallowed package [" + packageName + "]");
+ }
+ }
+ return mUserManager.createProfileForUserEvenWhenDisallowed(
+ params.getProfileName(),
+ UserManager.USER_TYPE_PROFILE_MANAGED,
+ UserInfo.FLAG_DISABLED,
+ userId,
+ nonRequiredApps.toArray(new String[nonRequiredApps.size()]));
+ }
+
+ @Override
+ public void finalizeCreateManagedProfile(
+ @NonNull ManagedProfileProvisioningParams provisioningParams,
+ @NonNull UserHandle managedProfileUser) {
+ Objects.requireNonNull(provisioningParams, "provisioningParams is null");
+ Objects.requireNonNull(managedProfileUser, "managedProfileUser is null");
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
+
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ finalizeCreateManagedProfileInternal(provisioningParams, managedProfileUser);
+ });
+ }
+
+ private void finalizeCreateManagedProfileInternal(
+ @NonNull ManagedProfileProvisioningParams provisioningParams,
+ @NonNull UserHandle managedProfileUser
+ ) {
+ onCreateAndProvisionManagedProfileCompleted(provisioningParams);
+ sendProvisioningCompletedBroadcast(
+ managedProfileUser.getIdentifier(),
+ ACTION_PROVISION_MANAGED_PROFILE,
+ provisioningParams.isLeaveAllSystemAppsEnabled());
+ }
+
+ @Override
public void finalizeWorkProfileProvisioning(UserHandle managedProfileUser,
Account migratedAccount) {
Preconditions.checkCallAuthorization(
@@ -21523,7 +21659,7 @@
private void pregrantDefaultInteractAcrossProfilesAppOps(@UserIdInt int userId) {
final String op =
- AppOpsManager.permissionToOp(Manifest.permission.INTERACT_ACROSS_PROFILES);
+ AppOpsManager.permissionToOp(permission.INTERACT_ACROSS_PROFILES);
for (String packageName : getConfigurableDefaultCrossProfilePackages(userId)) {
if (!appOpIsDefaultOrAllowed(userId, op, packageName)) {
continue;
@@ -21726,7 +21862,8 @@
Slogf.i(LOG_TAG, "Account removed from the primary user.");
} else {
// TODO(174768447): Revisit start activity logic.
- final Intent removeIntent = result.getParcelable(AccountManager.KEY_INTENT, android.content.Intent.class);
+ final Intent removeIntent =
+ result.getParcelable(AccountManager.KEY_INTENT, Intent.class);
removeIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
if (removeIntent != null) {
Slogf.i(LOG_TAG, "Starting activity to remove account");
@@ -22022,7 +22159,7 @@
}
synchronized (getLockObject()) {
mInjector.settingsGlobalPutStringForUser(
- Settings.Global.DEVICE_DEMO_MODE, Integer.toString(/* value= */ 1), userId);
+ Global.DEVICE_DEMO_MODE, Integer.toString(/* value= */ 1), userId);
}
setUserProvisioningState(STATE_USER_SETUP_FINALIZED, userId);
@@ -22285,7 +22422,7 @@
@Override
public boolean isDevicePotentiallyStolen(String callerPackageName) {
final CallerIdentity caller = getCallerIdentity(callerPackageName);
- if (!android.app.admin.flags.Flags.deviceTheftImplEnabled()) {
+ if (!Flags.deviceTheftImplEnabled()) {
return false;
}
enforcePermission(QUERY_DEVICE_STOLEN_STATE, caller.getPackageName(),
@@ -22321,7 +22458,7 @@
@Override
public void setDrawables(@NonNull List<DevicePolicyDrawableResource> drawables) {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
- android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
+ permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
Objects.requireNonNull(drawables, "drawables must be provided.");
@@ -22337,7 +22474,7 @@
@Override
public void resetDrawables(@NonNull List<String> drawableIds) {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
- android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
+ permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
Objects.requireNonNull(drawableIds, "drawableIds must be provided.");
@@ -22363,7 +22500,7 @@
@Override
public void setStrings(@NonNull List<DevicePolicyStringResource> strings) {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
- android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
+ permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
Objects.requireNonNull(strings, "strings must be provided.");
@@ -22378,7 +22515,7 @@
@Override
public void resetStrings(@NonNull List<String> stringIds) {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
- android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
+ permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
mInjector.binderWithCleanCallingIdentity(() -> {
if (mDeviceManagementResourcesProvider.removeStrings(stringIds)) {
@@ -22448,7 +22585,7 @@
@Override
public void resetShouldAllowBypassingDevicePolicyManagementRoleQualificationState() {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
- android.Manifest.permission.MANAGE_ROLE_HOLDERS));
+ permission.MANAGE_ROLE_HOLDERS));
setBypassDevicePolicyManagementRoleQualificationStateInternal(
/* currentRoleHolder= */ null, /* allowBypass= */ false);
}
@@ -22456,7 +22593,7 @@
@Override
public boolean shouldAllowBypassingDevicePolicyManagementRoleQualification() {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
- android.Manifest.permission.MANAGE_ROLE_HOLDERS));
+ permission.MANAGE_ROLE_HOLDERS));
return mInjector.binderWithCleanCallingIdentity(() -> {
if (getUserData(
UserHandle.USER_SYSTEM).mBypassDevicePolicyManagementRoleQualifications) {
@@ -24049,7 +24186,7 @@
if (!isRuntimePermission(permission)) {
continue;
}
- int grantState = DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+ int grantState = PERMISSION_GRANT_STATE_DEFAULT;
try {
grantState = getPermissionGrantStateForUser(
packageInfo.packageName, permission,
@@ -24062,7 +24199,7 @@
Slogf.e(LOG_TAG, e, "Error retrieving permission grant state for %s "
+ "and %s", packageInfo.packageName, permission);
}
- if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
+ if (grantState == PERMISSION_GRANT_STATE_DEFAULT) {
// Not Controlled by a policy
continue;
}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
index 1be5cef..acd34e3 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
@@ -28,6 +28,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -60,6 +61,7 @@
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.List;
@AppModeFull
@AppModeNonSdkSandbox
@@ -124,7 +126,8 @@
@Test
public void changeNonExportedComponent_sendPackageChangedBroadcastToSystem_withPermission()
throws Exception {
- changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */);
+ changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */,
+ new String[0] /* sharedPackages */);
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
verify(mMockActivityManagerInternal).broadcastIntentWithCallback(
@@ -140,7 +143,8 @@
@Test
public void changeNonExportedComponent_sendPackageChangedBroadcastToApplicationItself()
throws Exception {
- changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */);
+ changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */,
+ new String[0] /* sharedPackages */);
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
verify(mMockActivityManagerInternal).broadcastIntentWithCallback(captor.capture(), eq(null),
@@ -150,9 +154,45 @@
assertThat(intent.getPackage()).isEqualTo(PACKAGE_CHANGED_TEST_PACKAGE_NAME);
}
+ @RequiresFlagsEnabled(FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES)
+ @Test
+ public void changeNonExportedComponent_sendPackageChangedBroadcastToSharedUserIdApplications()
+ throws Exception {
+ changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */,
+ new String[]{"shared.package"} /* sharedPackages */);
+
+ ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
+ ArgumentCaptor<String[]> captorRequiredPermissions = ArgumentCaptor.forClass(
+ String[].class);
+ verify(mMockActivityManagerInternal, times(3)).broadcastIntentWithCallback(
+ captorIntent.capture(), eq(null), captorRequiredPermissions.capture(), anyInt(),
+ eq(null), eq(null), eq(null));
+ List<Intent> intents = captorIntent.getAllValues();
+ List<String[]> requiredPermissions = captorRequiredPermissions.getAllValues();
+ assertNotNull(intents);
+ assertThat(intents.size()).isEqualTo(3);
+
+ final Intent intent1 = intents.get(0);
+ final String[] requiredPermission1 = requiredPermissions.get(0);
+ assertThat(intent1.getPackage()).isEqualTo("android");
+ assertThat(requiredPermission1).isEqualTo(
+ new String[]{PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED});
+
+ final Intent intent2 = intents.get(1);
+ final String[] requiredPermission2 = requiredPermissions.get(1);
+ assertThat(intent2.getPackage()).isEqualTo(PACKAGE_CHANGED_TEST_PACKAGE_NAME);
+ assertThat(requiredPermission2).isNull();
+
+ final Intent intent3 = intents.get(2);
+ final String[] requiredPermission3 = requiredPermissions.get(2);
+ assertThat(intent3.getPackage()).isEqualTo("shared.package");
+ assertThat(requiredPermission3).isNull();
+ }
+
@Test
public void changeExportedComponent_sendPackageChangedBroadcastToAll() throws Exception {
- changeComponentAndSendPackageChangedBroadcast(true /* changeExportedComponent */);
+ changeComponentAndSendPackageChangedBroadcast(true /* changeExportedComponent */,
+ new String[0] /* sharedPackages */);
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
verify(mMockActivityManagerInternal).broadcastIntentWithCallback(captor.capture(), eq(null),
@@ -162,11 +202,14 @@
assertNull(intent.getPackage());
}
- private void changeComponentAndSendPackageChangedBroadcast(boolean changeExportedComponent) {
+ private void changeComponentAndSendPackageChangedBroadcast(boolean changeExportedComponent,
+ String[] sharedPackages) {
when(mMockSnapshot.getPackageStateInternal(eq(PACKAGE_CHANGED_TEST_PACKAGE_NAME),
anyInt())).thenReturn(mMockPackageStateInternal);
when(mMockSnapshot.isInstantAppInternal(any(), anyInt(), anyInt())).thenReturn(false);
when(mMockSnapshot.getVisibilityAllowLists(any(), any())).thenReturn(null);
+ when(mMockSnapshot.getSharedUserPackagesForPackage(eq(PACKAGE_CHANGED_TEST_PACKAGE_NAME),
+ anyInt())).thenReturn(sharedPackages);
when(mMockPackageStateInternal.getPkg()).thenReturn(mMockAndroidPackageInternal);
when(mMockParsedActivity.getClassName()).thenReturn(
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index dcbc234..5a872ea 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -47,10 +47,8 @@
import static com.android.server.am.ProcessList.NETWORK_STATE_BLOCK;
import static com.android.server.am.ProcessList.NETWORK_STATE_NO_CHANGE;
import static com.android.server.am.ProcessList.NETWORK_STATE_UNBLOCK;
-
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -80,6 +78,7 @@
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.app.ApplicationThreadConstants;
import android.app.BackgroundStartPrivileges;
import android.app.BroadcastOptions;
import android.app.ForegroundServiceDelegationOptions;
@@ -87,6 +86,7 @@
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.SyncNotedAppOp;
+import android.app.backup.BackupAnnotations;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -111,6 +111,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.permission.IPermissionManager;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -133,6 +134,7 @@
import com.android.server.am.ProcessList.IsolatedUidRangeAllocator;
import com.android.server.am.UidObserverController.ChangeRecord;
import com.android.server.appop.AppOpsService;
+import com.android.server.job.JobSchedulerInternal;
import com.android.server.notification.NotificationManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.ActivityTaskManagerService;
@@ -228,6 +230,7 @@
@Mock private PackageManagerInternal mPackageManagerInternal;
@Mock private ActivityTaskManagerInternal mActivityTaskManagerInternal;
@Mock private NotificationManagerInternal mNotificationManagerInternal;
+ @Mock private JobSchedulerInternal mJobSchedulerInternal;
@Mock private ContentResolver mContentResolver;
private TestInjector mInjector;
@@ -249,6 +252,7 @@
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInternal);
LocalServices.addService(NotificationManagerInternal.class, mNotificationManagerInternal);
+ LocalServices.addService(JobSchedulerInternal.class, mJobSchedulerInternal);
doReturn(new ComponentName("", "")).when(mPackageManagerInternal)
.getSystemUiServiceComponent();
@@ -308,6 +312,7 @@
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
LocalServices.removeServiceForTest(NotificationManagerInternal.class);
+ LocalServices.removeServiceForTest(JobSchedulerInternal.class);
if (mMockingSession != null) {
mMockingSession.finishMocking();
@@ -1548,6 +1553,50 @@
eq(notificationId), anyInt());
}
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void bindBackupAgent_fullBackup_shouldUseRestrictedMode_setsInFullBackup()
+ throws Exception {
+ ActivityManagerService spyAms = spy(mAms);
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.packageName = TEST_PACKAGE;
+ applicationInfo.processName = TEST_PACKAGE;
+ applicationInfo.uid = TEST_UID;
+ doReturn(applicationInfo).when(mPackageManager).getApplicationInfo(eq(TEST_PACKAGE),
+ anyLong(), anyInt());
+ ProcessRecord appRec = new ProcessRecord(mAms, applicationInfo, TAG, TEST_UID);
+ doReturn(appRec).when(spyAms).getProcessRecordLocked(eq(TEST_PACKAGE), eq(TEST_UID));
+
+ spyAms.bindBackupAgent(TEST_PACKAGE, ApplicationThreadConstants.BACKUP_MODE_FULL,
+ UserHandle.USER_SYSTEM,
+ BackupAnnotations.BackupDestination.CLOUD, /* shouldUseRestrictedMode= */
+ true);
+
+ assertThat(appRec.isInFullBackup()).isTrue();
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void bindBackupAgent_fullBackup_shouldNotUseRestrictedMode_doesNotSetInFullBackup()
+ throws Exception {
+ ActivityManagerService spyAms = spy(mAms);
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.packageName = TEST_PACKAGE;
+ applicationInfo.processName = TEST_PACKAGE;
+ applicationInfo.uid = TEST_UID;
+ doReturn(applicationInfo).when(mPackageManager).getApplicationInfo(eq(TEST_PACKAGE),
+ anyLong(), anyInt());
+ ProcessRecord appRec = new ProcessRecord(mAms, applicationInfo, TAG, TEST_UID);
+ doReturn(appRec).when(spyAms).getProcessRecordLocked(eq(TEST_PACKAGE), eq(TEST_UID));
+
+ spyAms.bindBackupAgent(TEST_PACKAGE, ApplicationThreadConstants.BACKUP_MODE_FULL,
+ UserHandle.USER_SYSTEM,
+ BackupAnnotations.BackupDestination.CLOUD, /* shouldUseRestrictedMode= */
+ false);
+
+ assertThat(appRec.isInFullBackup()).isFalse();
+ }
+
private static class TestHandler extends Handler {
private static final long WAIT_FOR_MSG_TIMEOUT_MS = 4000; // 4 sec
private static final long WAIT_FOR_MSG_INTERVAL_MS = 400; // 0.4 sec
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index 1caa02a..5eb23a2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -58,6 +58,7 @@
import com.android.server.wm.ActivityTaskManagerService;
import org.junit.Rule;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -187,8 +188,8 @@
doReturn(true).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
eq(BroadcastFilter.RESTRICT_PRIORITY_VALUES), any(ApplicationInfo.class));
- doReturn(true).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), anyInt());
+ doReturn(true).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), any(ApplicationInfo.class));
}
public void tearDown() throws Exception {
@@ -308,4 +309,8 @@
app.mOptRecord.setPendingFreeze(pendingFreeze);
app.mOptRecord.setFrozen(frozen);
}
+
+ ArgumentMatcher<ApplicationInfo> appInfoEquals(int uid) {
+ return test -> (test.uid == uid);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 88caaa6..82237bc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -47,6 +47,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doAnswer;
@@ -184,7 +185,10 @@
}
private static BroadcastFilter makeMockRegisteredReceiver() {
- return mock(BroadcastFilter.class);
+ final BroadcastFilter filter = mock(BroadcastFilter.class);
+ final ApplicationInfo info = makeApplicationInfo(PACKAGE_ORANGE);
+ doReturn(info).when(filter).getApplicationInfo();
+ return filter;
}
private BroadcastRecord makeBroadcastRecord(Intent intent) {
@@ -716,9 +720,9 @@
@EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
@Test
public void testRunnableAt_Cached_Prioritized_NonDeferrable_changeIdDisabled() {
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
- eq(getUidForPackage(PACKAGE_GREEN)));
+ argThat(appInfoEquals(getUidForPackage(PACKAGE_GREEN))));
final List receivers = List.of(
withPriority(makeManifestReceiver(PACKAGE_RED, PACKAGE_RED), 10),
withPriority(makeManifestReceiver(PACKAGE_GREEN, PACKAGE_GREEN), -10));
@@ -1288,9 +1292,9 @@
@SuppressWarnings("GuardedBy")
@Test
public void testDeliveryGroupPolicy_prioritized_diffReceivers_changeIdDisabled() {
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
- eq(getUidForPackage(PACKAGE_GREEN)));
+ argThat(appInfoEquals(getUidForPackage(PACKAGE_GREEN))));
final Intent screenOn = new Intent(Intent.ACTION_SCREEN_ON);
final Intent screenOff = new Intent(Intent.ACTION_SCREEN_OFF);
@@ -1823,9 +1827,9 @@
@SuppressWarnings("GuardedBy")
@Test
public void testDeliveryDeferredForCached_changeIdDisabled() throws Exception {
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
- eq(getUidForPackage(PACKAGE_GREEN)));
+ argThat(appInfoEquals(getUidForPackage(PACKAGE_GREEN))));
final ProcessRecord greenProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
final ProcessRecord redProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_RED));
@@ -2027,9 +2031,9 @@
@Test
public void testDeliveryDeferredForCached_withInfiniteDeferred_changeIdDisabled()
throws Exception {
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
- eq(getUidForPackage(PACKAGE_GREEN)));
+ argThat(appInfoEquals(getUidForPackage(PACKAGE_GREEN))));
final ProcessRecord greenProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
final ProcessRecord redProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_RED));
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index a38ef78..ea80f28 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -1659,8 +1659,9 @@
final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(receiverBlueApp.uid));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
+ argThat(appInfoEquals(receiverBlueApp.uid)));
// Enqueue a normal broadcast that will go to several processes, and
// then enqueue a foreground broadcast that risks reordering
@@ -2471,8 +2472,9 @@
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(receiverBlueApp.uid));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
+ argThat(appInfoEquals(receiverBlueApp.uid)));
mUidObserver.onUidStateChanged(receiverGreenApp.info.uid,
ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index f9f3790..8482fd6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -19,12 +19,12 @@
import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.server.am.BroadcastRecord.LIMIT_PRIORITY_SCOPE;
import static com.android.server.am.BroadcastRecord.DELIVERY_DEFERRED;
import static com.android.server.am.BroadcastRecord.DELIVERY_DELIVERED;
import static com.android.server.am.BroadcastRecord.DELIVERY_PENDING;
import static com.android.server.am.BroadcastRecord.DELIVERY_SKIPPED;
import static com.android.server.am.BroadcastRecord.DELIVERY_TIMEOUT;
+import static com.android.server.am.BroadcastRecord.LIMIT_PRIORITY_SCOPE;
import static com.android.server.am.BroadcastRecord.calculateBlockedUntilBeyondCount;
import static com.android.server.am.BroadcastRecord.calculateDeferUntilActive;
import static com.android.server.am.BroadcastRecord.calculateUrgent;
@@ -35,7 +35,8 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import android.app.BackgroundStartPrivileges;
@@ -63,6 +64,7 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;
@@ -108,8 +110,8 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- doReturn(true).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), anyInt());
+ doReturn(true).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), any(ApplicationInfo.class));
}
@Test
@@ -222,8 +224,8 @@
@EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
@Test
public void testIsPrioritized_withDifferentPriorities_withFirstUidChangeIdDisabled() {
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(1)));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(1))));
assertTrue(isPrioritized(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 10),
@@ -256,8 +258,8 @@
@EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
@Test
public void testIsPrioritized_withDifferentPriorities_withLastUidChangeIdDisabled() {
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(3)));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(3))));
assertTrue(isPrioritized(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 10),
@@ -294,8 +296,8 @@
@EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
@Test
public void testIsPrioritized_withDifferentPriorities_withUidChangeIdDisabled() {
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(2)));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(2))));
assertTrue(isPrioritized(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 10),
@@ -328,10 +330,10 @@
@EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
@Test
public void testIsPrioritized_withDifferentPriorities_withMultipleUidChangeIdDisabled() {
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(1)));
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(2)));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(1))));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(2))));
assertTrue(isPrioritized(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 10),
@@ -362,10 +364,10 @@
assertArrayEquals(new int[] {0, 0, 1, 1, 3},
calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 20),
- createResolveInfo(PACKAGE2, getAppId(3), 20),
+ createResolveInfo(PACKAGE3, getAppId(3), 20),
createResolveInfo(PACKAGE3, getAppId(3), 10),
createResolveInfo(PACKAGE3, getAppId(3), 0),
- createResolveInfo(PACKAGE3, getAppId(2), 0)), false, mPlatformCompat));
+ createResolveInfo(PACKAGE2, getAppId(2), 0)), false, mPlatformCompat));
}
@Test
@@ -592,8 +594,8 @@
@EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
@Test
public void testSetDeliveryState_DeferUntilActive_changeIdDisabled() {
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(1)));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(1))));
final BroadcastRecord r = createBroadcastRecord(
new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
createResolveInfo(PACKAGE1, getAppId(1), 10),
@@ -960,8 +962,8 @@
createResolveInfo(PACKAGE2, getAppId(2)),
createResolveInfo(PACKAGE3, getAppId(3)))));
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(1)));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(1))));
assertArrayEquals(new boolean[] {false, true, true}, calculateChangeState(
List.of(createResolveInfo(PACKAGE1, getAppId(1)),
createResolveInfo(PACKAGE2, getAppId(2)),
@@ -969,11 +971,11 @@
assertArrayEquals(new boolean[] {false, true, false, true}, calculateChangeState(
List.of(createResolveInfo(PACKAGE1, getAppId(1)),
createResolveInfo(PACKAGE2, getAppId(2)),
- createResolveInfo(PACKAGE2, getAppId(1)),
+ createResolveInfo(PACKAGE1, getAppId(1)),
createResolveInfo(PACKAGE3, getAppId(3)))));
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(2)));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(2))));
assertArrayEquals(new boolean[] {false, false, true}, calculateChangeState(
List.of(createResolveInfo(PACKAGE1, getAppId(1)),
createResolveInfo(PACKAGE2, getAppId(2)),
@@ -987,8 +989,8 @@
createResolveInfo(PACKAGE2, getAppId(2)),
createResolveInfo(PACKAGE3, getAppId(3)))));
- doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
- eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(3)));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledInternalNoLogging(
+ eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), argThat(appInfoEquals(getAppId(3))));
assertArrayEquals(new boolean[] {false, false, false}, calculateChangeState(
List.of(createResolveInfo(PACKAGE1, getAppId(1)),
createResolveInfo(PACKAGE2, getAppId(2)),
@@ -998,7 +1000,7 @@
List.of(createResolveInfo(PACKAGE1, getAppId(1)),
createResolveInfo(PACKAGE3, getAppId(3)),
createResolveInfo(PACKAGE2, getAppId(2)),
- createResolveInfo(PACKAGE2, getAppId(1)),
+ createResolveInfo(PACKAGE1, getAppId(1)),
createResolveInfo(PACKAGE2, getAppId(2)),
createResolveInfo(PACKAGE3, getAppId(3)))));
}
@@ -1185,4 +1187,8 @@
assertEquals("deferred", expectedDeferredCount, r.deferredCount);
assertEquals("beyond", expectedBeyondCount, r.beyondCount);
}
+
+ private ArgumentMatcher<ApplicationInfo> appInfoEquals(int uid) {
+ return test -> (test.uid == uid);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index f82a860..a9569b4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -1427,6 +1427,31 @@
@SuppressWarnings("GuardedBy")
@Test
+ public void testUpdateOomAdj_DoOne_Service_NotPerceptible_AboveClient() {
+ ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+ ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+ MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+ ProcessRecord service = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
+ MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
+ bindService(app, client, null, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class));
+ bindService(service, app, null, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
+ mProcessStateController.setRunningRemoteAnimation(client, true);
+ mProcessStateController.updateHasAboveClientLocked(app.mServices);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ updateOomAdj(client, app, service);
+
+ final int expectedAdj;
+ if (Flags.addModifyRawOomAdjServiceLevel()) {
+ expectedAdj = SERVICE_ADJ;
+ } else {
+ expectedAdj = CACHED_APP_MIN_ADJ;
+ }
+ assertEquals(expectedAdj, app.mState.getSetAdj());
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
public void testUpdateOomAdj_DoOne_Service_NotVisible() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
@@ -2906,7 +2931,7 @@
// Simulate binding to a service in the same process using BIND_ABOVE_CLIENT and
// verify that its OOM adjustment level is unaffected.
bindService(service, app, null, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
- app.mServices.updateHasAboveClientLocked();
+ mProcessStateController.updateHasAboveClientLocked(app.mServices);
assertTrue(app.mServices.hasAboveClient());
updateOomAdj(app);
@@ -2928,7 +2953,7 @@
// Simulate binding to a service in the same process using BIND_ABOVE_CLIENT and
// verify that its OOM adjustment level is unaffected.
bindService(app, app, null, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
- app.mServices.updateHasAboveClientLocked();
+ mProcessStateController.updateHasAboveClientLocked(app.mServices);
assertFalse(app.mServices.hasAboveClient());
updateOomAdj(app);
@@ -2983,7 +3008,7 @@
// Since sr.app is null, this service cannot be in the same process as the
// client so we expect the BIND_ABOVE_CLIENT adjustment to take effect.
- app.mServices.updateHasAboveClientLocked();
+ mProcessStateController.updateHasAboveClientLocked(app.mServices);
updateOomAdj(app);
assertTrue(app.mServices.hasAboveClient());
assertNotEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
@@ -3306,7 +3331,7 @@
if (Flags.pushGlobalStateToOomadjuster()) {
mProcessStateController.setBackupTarget(app, app.userId);
} else {
- BackupRecord backupTarget = new BackupRecord(null, 0, 0, 0);
+ BackupRecord backupTarget = new BackupRecord(null, 0, 0, 0, true);
backupTarget.app = app;
doReturn(backupTarget).when(mService.mBackupTargets).get(anyInt());
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 65286d9..07f2188 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -18,9 +18,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-
import static com.google.common.truth.Truth.assertThat;
-
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -32,20 +30,27 @@
import static org.mockito.Mockito.when;
import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
+import android.app.ApplicationThreadConstants;
+import android.app.IActivityManager;
import android.app.backup.BackupAgent;
-import android.app.backup.BackupAnnotations;
import android.app.backup.BackupAnnotations.BackupDestination;
+import android.app.backup.BackupAnnotations.OperationType;
import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IBackupObserver;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
+import android.compat.testing.PlatformCompatChangeRule;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Handler;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.testing.TestableContext;
import android.util.FeatureFlagUtils;
@@ -68,7 +73,9 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -77,8 +84,12 @@
import java.util.Arrays;
import java.util.List;
+import java.util.Set;
import java.util.function.IntConsumer;
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
@Presubmit
@RunWith(AndroidJUnit4.class)
public class UserBackupManagerServiceTest {
@@ -88,6 +99,11 @@
private static final int WORKER_THREAD_TIMEOUT_MILLISECONDS = 100;
@UserIdInt private static final int USER_ID = 0;
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock IBackupManagerMonitor mBackupManagerMonitor;
@Mock IBackupObserver mBackupObserver;
@Mock PackageManager mPackageManager;
@@ -99,10 +115,14 @@
@Mock JobScheduler mJobScheduler;
@Mock BackupHandler mBackupHandler;
@Mock BackupManagerMonitorEventSender mBackupManagerMonitorEventSender;
+ @Mock IActivityManager mActivityManager;
+ @Mock
+ ActivityManagerInternal mActivityManagerInternal;
private TestableContext mContext;
private MockitoSession mSession;
private TestBackupService mService;
+ private ApplicationInfo mTestPackageApplicationInfo;
@Before
public void setUp() throws Exception {
@@ -120,12 +140,14 @@
mContext.getTestablePermissions().setPermission(android.Manifest.permission.BACKUP,
PackageManager.PERMISSION_GRANTED);
- mService = new TestBackupService(mContext, mPackageManager, mOperationStorage,
- mTransportManager, mBackupHandler);
+ mService = new TestBackupService();
mService.setEnabled(true);
mService.setSetupComplete(true);
mService.enqueueFullBackup("com.test.backup.app", /* lastBackedUp= */ 0);
- }
+
+ mTestPackageApplicationInfo = new ApplicationInfo();
+ mTestPackageApplicationInfo.packageName = TEST_PACKAGE;
+ }
@After
public void tearDown() {
@@ -298,9 +320,160 @@
new DataTypeResult(/* dataType */ "type_2"));
mService.reportDelayedRestoreResult(TEST_PACKAGE, results);
-
verify(mBackupManagerMonitorEventSender).sendAgentLoggingResults(
- eq(packageInfo), eq(results), eq(BackupAnnotations.OperationType.RESTORE));
+ eq(packageInfo), eq(results), eq(OperationType.RESTORE));
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public void bindToAgentSynchronous_restrictedModeChangesFlagOff_shouldUseRestrictedMode()
+ throws Exception {
+ mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(true));
+ // Make sure we never hit the code that checks the property.
+ verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public void bindToAgentSynchronous_keyValueBackup_shouldNotUseRestrictedMode()
+ throws Exception {
+ mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(false));
+ // Make sure we never hit the code that checks the property.
+ verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public void bindToAgentSynchronous_keyValueRestore_shouldNotUseRestrictedMode()
+ throws Exception {
+ mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_RESTORE, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(false));
+ // Make sure we never hit the code that checks the property.
+ verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public void bindToAgentSynchronous_packageOptedIn_shouldUseRestrictedMode()
+ throws Exception {
+ when(mPackageManager.getPropertyAsUser(
+ eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE),
+ eq(TEST_PACKAGE), any(), anyInt())).thenReturn(new PackageManager.Property(
+ PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE, /* value= */ true,
+ TEST_PACKAGE, /* className= */ null));
+
+ mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(true));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public void bindToAgentSynchronous_packageOptedOut_shouldNotUseRestrictedMode()
+ throws Exception {
+ when(mPackageManager.getPropertyAsUser(
+ eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE),
+ eq(TEST_PACKAGE), any(), anyInt())).thenReturn(new PackageManager.Property(
+ PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE, /* value= */ false,
+ TEST_PACKAGE, /* className= */ null));
+
+ mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(false));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ @DisableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE})
+ public void bindToAgentSynchronous_targetSdkBelowB_shouldUseRestrictedMode()
+ throws Exception {
+ // Mock that the app has not explicitly set the property.
+ when(mPackageManager.getPropertyAsUser(
+ eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE),
+ eq(TEST_PACKAGE), any(), anyInt())).thenThrow(
+ new PackageManager.NameNotFoundException()
+ );
+
+ mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(true));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ @EnableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE})
+ public void bindToAgentSynchronous_targetSdkB_notInList_shouldUseRestrictedMode()
+ throws Exception {
+ // Mock that the app has not explicitly set the property.
+ when(mPackageManager.getPropertyAsUser(
+ eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE),
+ eq(TEST_PACKAGE), any(), anyInt())).thenThrow(
+ new PackageManager.NameNotFoundException()
+ );
+ mService.clearNoRestrictedModePackages();
+
+ mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(true));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ @EnableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE})
+ public void bindToAgentSynchronous_forRestore_targetSdkB_inList_shouldNotUseRestrictedMode()
+ throws Exception {
+ // Mock that the app has not explicitly set the property.
+ when(mPackageManager.getPropertyAsUser(
+ eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE),
+ eq(TEST_PACKAGE), any(), anyInt())).thenThrow(
+ new PackageManager.NameNotFoundException()
+ );
+ mService.setNoRestrictedModePackages(Set.of(TEST_PACKAGE), OperationType.RESTORE);
+
+ mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(false));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ @EnableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE})
+ public void bindToAgentSynchronous_forBackup_targetSdkB_inList_shouldNotUseRestrictedMode()
+ throws Exception {
+ // Mock that the app has not explicitly set the property.
+ when(mPackageManager.getPropertyAsUser(
+ eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE),
+ eq(TEST_PACKAGE), any(), anyInt())).thenThrow(
+ new PackageManager.NameNotFoundException()
+ );
+ mService.setNoRestrictedModePackages(Set.of(TEST_PACKAGE), OperationType.BACKUP);
+
+ mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(false));
}
private static PackageInfo getPackageInfo(String packageName) {
@@ -316,11 +489,9 @@
private volatile Thread mWorkerThread = null;
- TestBackupService(Context context, PackageManager packageManager,
- LifecycleOperationStorage operationStorage, TransportManager transportManager,
- BackupHandler backupHandler) {
- super(context, packageManager, operationStorage, transportManager, backupHandler,
- createConstants(context));
+ TestBackupService() {
+ super(mContext, mPackageManager, mOperationStorage, mTransportManager, mBackupHandler,
+ createConstants(mContext), mActivityManager, mActivityManagerInternal);
}
private static BackupManagerConstants createConstants(Context context) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java
index 9474253..3310573 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java
@@ -18,34 +18,95 @@
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.when;
+import android.app.backup.BackupAnnotations;
+import android.app.backup.BackupTransport;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.platform.test.annotations.Presubmit;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.backup.BackupAgentTimeoutParameters;
+import com.android.server.backup.OperationStorage;
import com.android.server.backup.TransportManager;
import com.android.server.backup.UserBackupManagerService;
+import com.android.server.backup.transport.BackupTransportClient;
+import com.android.server.backup.transport.TransportConnection;
+import com.android.server.backup.utils.BackupEligibilityRules;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+
@Presubmit
@RunWith(AndroidJUnit4.class)
public class PerformFullTransportBackupTaskTest {
+ private static final String TEST_PACKAGE_1 = "package1";
+ private static final String TEST_PACKAGE_2 = "package2";
+
+ @Mock
+ BackupAgentTimeoutParameters mBackupAgentTimeoutParameters;
+ @Mock
+ BackupEligibilityRules mBackupEligibilityRules;
@Mock
UserBackupManagerService mBackupManagerService;
@Mock
+ BackupTransportClient mBackupTransportClient;
+ @Mock
+ CountDownLatch mLatch;
+ @Mock
+ OperationStorage mOperationStorage;
+ @Mock
+ PackageManager mPackageManager;
+ @Mock
+ TransportConnection mTransportConnection;
+ @Mock
TransportManager mTransportManager;
+ @Mock
+ UserBackupManagerService.BackupWakeLock mWakeLock;
+
+ private final List<String> mEligiblePackages = new ArrayList<>();
+
+ private PerformFullTransportBackupTask mTask;
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ when(mBackupManagerService.getPackageManager()).thenReturn(mPackageManager);
+ when(mBackupManagerService.getQueueLock()).thenReturn("something!");
+ when(mBackupManagerService.isEnabled()).thenReturn(true);
+ when(mBackupManagerService.getWakelock()).thenReturn(mWakeLock);
+ when(mBackupManagerService.isSetupComplete()).thenReturn(true);
+ when(mBackupManagerService.getAgentTimeoutParameters()).thenReturn(
+ mBackupAgentTimeoutParameters);
when(mBackupManagerService.getTransportManager()).thenReturn(mTransportManager);
+ when(mTransportManager.getCurrentTransportClient(any())).thenReturn(mTransportConnection);
+ when(mTransportConnection.connectOrThrow(any())).thenReturn(mBackupTransportClient);
+ when(mTransportConnection.connect(any())).thenReturn(mBackupTransportClient);
+ when(mBackupTransportClient.performFullBackup(any(), any(), anyInt())).thenReturn(
+ BackupTransport.TRANSPORT_ERROR);
+ when(mBackupEligibilityRules.appIsEligibleForBackup(
+ argThat(app -> mEligiblePackages.contains(app.packageName)))).thenReturn(
+ true);
+ when(mBackupEligibilityRules.appGetsFullBackup(
+ argThat(app -> mEligiblePackages.contains(app.packageName)))).thenReturn(
+ true);
}
@Test
@@ -70,4 +131,49 @@
/* backupEligibilityRules */ null);
});
}
+
+ @Test
+ public void run_setsAndClearsNoRestrictedModePackages() throws Exception {
+ mockPackageEligibleForFullBackup(TEST_PACKAGE_1);
+ mockPackageEligibleForFullBackup(TEST_PACKAGE_2);
+ createTask(new String[] {TEST_PACKAGE_1, TEST_PACKAGE_2});
+ when(mBackupTransportClient.getPackagesThatShouldNotUseRestrictedMode(any(),
+ anyInt())).thenReturn(Set.of("package1"));
+
+ mTask.run();
+
+ InOrder inOrder = inOrder(mBackupManagerService);
+ inOrder.verify(mBackupManagerService).setNoRestrictedModePackages(
+ eq(Set.of("package1")),
+ eq(BackupAnnotations.OperationType.BACKUP));
+ inOrder.verify(mBackupManagerService).clearNoRestrictedModePackages();
+ }
+
+ private void createTask(String[] packageNames) {
+ mTask = PerformFullTransportBackupTask
+ .newWithCurrentTransport(
+ mBackupManagerService,
+ mOperationStorage,
+ /* observer */ null,
+ /* whichPackages */ packageNames,
+ /* updateSchedule */ false,
+ /* runningJob */ null,
+ mLatch,
+ /* backupObserver */ null,
+ /* monitor */ null,
+ /* userInitiated */ false,
+ /* caller */ null,
+ mBackupEligibilityRules);
+ }
+
+ private void mockPackageEligibleForFullBackup(String packageName) throws Exception {
+ mEligiblePackages.add(packageName);
+ ApplicationInfo appInfo = new ApplicationInfo();
+ appInfo.packageName = packageName;
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = packageName;
+ packageInfo.applicationInfo = appInfo;
+ when(mPackageManager.getPackageInfoAsUser(eq(packageName), anyInt(), anyInt())).thenReturn(
+ packageInfo);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
index 414532b..055adf6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
@@ -23,8 +23,10 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.backup.BackupAnnotations;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.app.backup.BackupTransport;
@@ -91,6 +93,8 @@
private UserBackupManagerService mBackupManagerService;
@Mock
private TransportConnection mTransportConnection;
+ @Mock
+ private BackupTransportClient mBackupTransportClient;
private Set<String> mExcludedkeys = new HashSet<>();
private Map<String, String> mBackupData = new HashMap<>();
@@ -151,6 +155,23 @@
}
@Test
+ public void setNoRestrictedModePackages_callsTransportAndSetsValue() throws Exception {
+ PackageInfo packageInfo1 = new PackageInfo();
+ packageInfo1.packageName = "package1";
+ PackageInfo packageInfo2 = new PackageInfo();
+ packageInfo2.packageName = "package2";
+ when(mBackupTransportClient.getPackagesThatShouldNotUseRestrictedMode(any(),
+ anyInt())).thenReturn(Set.of("package1"));
+
+ mRestoreTask.setNoRestrictedModePackages(mBackupTransportClient,
+ new PackageInfo[]{packageInfo1, packageInfo2});
+
+ verify(mBackupManagerService).setNoRestrictedModePackages(
+ eq(Set.of("package1")),
+ eq(BackupAnnotations.OperationType.RESTORE));
+ }
+
+ @Test
public void testFilterExcludedKeys() throws Exception {
when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME)))
.thenReturn(mExcludedkeys);
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
index 2d7d46f..13e3207 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
@@ -19,7 +19,14 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import android.app.backup.BackupAnnotations.OperationType;
import android.app.backup.BackupTransport;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.RestoreDescription;
@@ -38,15 +45,31 @@
import com.android.internal.backup.ITransportStatusCallback;
import com.android.internal.infra.AndroidFuture;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.util.List;
+import java.util.Set;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class BackupTransportClientTest {
+ @Mock
+ IBackupTransport mMockBackupTransport;
+
+ private BackupTransportClient mMockingTransportClient;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mMockingTransportClient = new BackupTransportClient(
+ mMockBackupTransport);
+ }
+
private static class TestFuturesFakeTransportBinder extends FakeTransportBinderBase {
public final Object mLock = new Object();
@@ -128,6 +151,70 @@
thread.join();
}
+ @Test
+ public void getPackagesThatShouldNotUseRestrictedMode_passesSetAsListToBinder()
+ throws Exception {
+ mockGetPackagesThatShouldNotUseRestrictedModeReturn(List.of("package1", "package2"));
+
+ mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode(
+ Set.of("package1", "package2"),
+ OperationType.BACKUP);
+
+ verify(mMockBackupTransport).getPackagesThatShouldNotUseRestrictedMode(
+ argThat(list -> Set.copyOf(list).equals(Set.of("package1", "package2"))),
+ eq(OperationType.BACKUP), any());
+ }
+
+ @Test
+ public void getPackagesThatShouldNotUseRestrictedMode_forRestore_callsBinderForRestore()
+ throws Exception {
+ mockGetPackagesThatShouldNotUseRestrictedModeReturn(null);
+
+ mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode(
+ Set.of(),
+ OperationType.RESTORE);
+
+ verify(mMockBackupTransport).getPackagesThatShouldNotUseRestrictedMode(any(),
+ eq(OperationType.RESTORE), any());
+ }
+
+ @Test
+ public void getPackagesThatShouldNotUseRestrictedMode_forBackup_callsBinderForBackup()
+ throws Exception {
+ mockGetPackagesThatShouldNotUseRestrictedModeReturn(null);
+
+ mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode(
+ Set.of(),
+ OperationType.BACKUP);
+
+ verify(mMockBackupTransport).getPackagesThatShouldNotUseRestrictedMode(any(),
+ eq(OperationType.BACKUP), any());
+ }
+
+ @Test
+ public void getPackagesThatShouldNotUseRestrictedMode_nullResult_returnsEmptySet()
+ throws Exception {
+ mockGetPackagesThatShouldNotUseRestrictedModeReturn(null);
+
+ Set<String> result = mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode(
+ Set.of(),
+ OperationType.BACKUP);
+
+ assertThat(result).isEqualTo(Set.of());
+ }
+
+ @Test
+ public void getPackagesThatShouldNotUseRestrictedMode_returnsResultAsSet()
+ throws Exception {
+ mockGetPackagesThatShouldNotUseRestrictedModeReturn(List.of("package1", "package2"));
+
+ Set<String> result = mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode(
+ Set.of("package1", "package2"),
+ OperationType.BACKUP);
+
+ assertThat(result).isEqualTo(Set.of("package1", "package2"));
+ }
+
private static class TestCallbacksFakeTransportBinder extends FakeTransportBinderBase {
public final Object mLock = new Object();
@@ -158,7 +245,6 @@
assertThat(status).isEqualTo(123);
}
-
@Test
public void testFinishBackup_completesLater_returnsStatus() throws Exception {
TestCallbacksFakeTransportBinder binder = new TestCallbacksFakeTransportBinder();
@@ -211,6 +297,14 @@
thread.join();
}
+ private void mockGetPackagesThatShouldNotUseRestrictedModeReturn(List<String> returnList)
+ throws Exception {
+ doAnswer(
+ i -> ((AndroidFuture<List<String>>) i.getArguments()[2]).complete(returnList)).when(
+ mMockBackupTransport).getPackagesThatShouldNotUseRestrictedMode(any(), anyInt(),
+ any());
+ }
+
// Convenience layer so we only need to fake specific methods useful for each test case.
private static class FakeTransportBinderBase implements IBackupTransport {
@Override public void name(AndroidFuture<String> f) throws RemoteException {}
@@ -258,6 +352,10 @@
@Override
public void getBackupManagerMonitor(AndroidFuture<IBackupManagerMonitor> resultFuture)
throws RemoteException {}
+ @Override
+ public void getPackagesThatShouldNotUseRestrictedMode(List<String> packageNames,
+ int operationType, AndroidFuture<List<String>> resultFuture)
+ throws RemoteException {}
@Override public IBinder asBinder() {
return null;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
index f6c644e..20ac078 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.content.Context;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.parsing.ApkLite;
import android.content.pm.parsing.ApkLiteParseUtils;
@@ -34,6 +35,8 @@
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.os.FileUtils;
+import android.os.Handler;
+import android.os.Looper;
import android.os.OutcomeReceiver;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -71,13 +74,17 @@
private static final String PUSH_FILE_DIR = "/data/local/tmp/tests/smockingservicestest/pm/";
private static final String TEST_APP_USING_SDK1_AND_SDK2 = "HelloWorldUsingSdk1And2.apk";
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
@Mock private SharedLibrariesImpl mSharedLibraries;
+ @Mock private Context mContext;
+ @Mock private Computer mComputer;
private InstallDependencyHelper mInstallDependencyHelper;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mInstallDependencyHelper = new InstallDependencyHelper(mSharedLibraries);
+ mInstallDependencyHelper = new InstallDependencyHelper(mContext, mSharedLibraries);
}
@Test
@@ -88,7 +95,8 @@
PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false);
- mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
+ mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer,
+ 0, mHandler, callback);
callback.assertFailure();
assertThat(callback.error).hasMessageThat().contains("xyz");
@@ -104,11 +112,12 @@
.thenReturn(missingDependency);
CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false);
- mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
+ mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer,
+ 0, mHandler, callback);
callback.assertFailure();
assertThat(callback.error).hasMessageThat().contains(
- "Failed to bind to Dependency Installer");
+ "Dependency Installer Service not found");
}
@@ -121,7 +130,8 @@
.thenReturn(missingDependency);
CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ true);
- mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
+ mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer,
+ 0, mHandler, callback);
callback.assertSuccess();
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
index b81bf3c..f6f831f 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -21,6 +21,7 @@
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS;
+import static android.hardware.biometrics.BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE;
import static com.android.server.biometrics.sensors.LockoutTracker.LOCKOUT_NONE;
@@ -354,6 +355,21 @@
assertThat(preAuthInfo.getIsMandatoryBiometricsAuthentication()).isTrue();
}
+ @Test
+ public void prioritizeStrengthErrorBeforeCameraUnavailableError() throws Exception {
+ final BiometricSensor sensor = getFaceSensorWithStrength(
+ BiometricManager.Authenticators.BIOMETRIC_WEAK);
+ final PromptInfo promptInfo = new PromptInfo();
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+ promptInfo.setNegativeButtonText(TEST_PACKAGE_NAME);
+ final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+ mSettingObserver, List.of(sensor), USER_ID , promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager,
+ mUserManager);
+
+ assertThat(preAuthInfo.getCanAuthenticateResult()).isEqualTo(BIOMETRIC_ERROR_NO_HARDWARE);
+ }
+
private BiometricSensor getFingerprintSensor() {
BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FINGERPRINT,
TYPE_FINGERPRINT, BiometricManager.Authenticators.BIOMETRIC_STRONG,
@@ -372,9 +388,10 @@
return sensor;
}
- private BiometricSensor getFaceSensor() {
+ private BiometricSensor getFaceSensorWithStrength(
+ @BiometricManager.Authenticators.Types int sensorStrength) {
BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
- BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
+ sensorStrength, mFaceAuthenticator) {
@Override
boolean confirmationAlwaysRequired(int userId) {
return false;
@@ -388,4 +405,8 @@
return sensor;
}
+
+ private BiometricSensor getFaceSensor() {
+ return getFaceSensorWithStrength(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java
index 89d2d28..affcfc1 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java
@@ -194,12 +194,14 @@
@Test
public void testExemptFromStoppingNullProjection() throws Exception {
- assertThat(mStopController.isExemptFromStopping(null)).isTrue();
+ assertThat(mStopController.isExemptFromStopping(null,
+ MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
}
@Test
public void testExemptFromStoppingInvalidProjection() throws Exception {
- assertThat(mStopController.isExemptFromStopping(createMediaProjection(null))).isTrue();
+ assertThat(mStopController.isExemptFromStopping(createMediaProjection(null),
+ MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
}
@Test
@@ -213,7 +215,8 @@
Settings.Global.putInt(mContext.getContentResolver(),
DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 1);
- assertThat(mStopController.isExemptFromStopping(mediaProjection)).isTrue();
+ assertThat(mStopController.isExemptFromStopping(mediaProjection,
+ MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
} finally {
Settings.Global.putInt(mContext.getContentResolver(),
DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, value);
@@ -230,7 +233,8 @@
eq(mediaProjection.uid), eq(mediaProjection.packageName),
nullable(String.class),
nullable(String.class));
- assertThat(mStopController.isExemptFromStopping(mediaProjection)).isTrue();
+ assertThat(mStopController.isExemptFromStopping(mediaProjection,
+ MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
}
@Test
@@ -244,7 +248,8 @@
doReturn(PackageManager.PERMISSION_DENIED).when(
mPackageManager).checkPermission(
RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
- assertThat(mStopController.isExemptFromStopping(mediaProjection)).isTrue();
+ assertThat(mStopController.isExemptFromStopping(mediaProjection,
+ MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -261,7 +266,8 @@
packages.valueAt(0));
doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
- assertThat(mStopController.isExemptFromStopping(mediaProjection)).isTrue();
+ assertThat(mStopController.isExemptFromStopping(mediaProjection,
+ MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
}
@Test
@@ -270,7 +276,8 @@
PACKAGE_NAME);
doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
- assertThat(mStopController.isExemptFromStopping(mediaProjection)).isTrue();
+ assertThat(mStopController.isExemptFromStopping(mediaProjection,
+ MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
}
@Test
@@ -278,7 +285,8 @@
MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager).checkPermission(
RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
- assertThat(mStopController.isExemptFromStopping(mediaProjection)).isTrue();
+ assertThat(mStopController.isExemptFromStopping(mediaProjection,
+ MediaProjectionStopController.STOP_REASON_UNKNOWN)).isTrue();
}
@Test
@@ -287,7 +295,8 @@
mediaProjection.notifyVirtualDisplayCreated(1);
doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
- assertThat(mStopController.isExemptFromStopping(mediaProjection)).isFalse();
+ assertThat(mStopController.isExemptFromStopping(mediaProjection,
+ MediaProjectionStopController.STOP_REASON_UNKNOWN)).isFalse();
}
@Test
@@ -368,6 +377,36 @@
verify(mStopConsumer).accept(MediaProjectionStopController.STOP_REASON_CALL_END);
}
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_STOP_MEDIA_PROJECTION_ON_CALL_END)
+ public void testExemptFromStopping_callEnd_callBeforeMediaProjection() throws Exception {
+ when(mTelecomManager.isInCall()).thenReturn(true);
+ mStopController.callStateChanged();
+
+ MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
+ mediaProjection.notifyVirtualDisplayCreated(1);
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+
+ assertThat(mStopController.isExemptFromStopping(mediaProjection,
+ MediaProjectionStopController.STOP_REASON_CALL_END)).isFalse();
+ }
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_STOP_MEDIA_PROJECTION_ON_CALL_END)
+ public void testExemptFromStopping_callEnd_callAfterMediaProjection() throws Exception {
+ MediaProjectionManagerService.MediaProjection mediaProjection = createMediaProjection();
+ mediaProjection.notifyVirtualDisplayCreated(1);
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, mediaProjection.packageName);
+
+ when(mTelecomManager.isInCall()).thenReturn(true);
+ mStopController.callStateChanged();
+
+ assertThat(mStopController.isExemptFromStopping(mediaProjection,
+ MediaProjectionStopController.STOP_REASON_CALL_END)).isTrue();
+ }
+
private MediaProjectionManagerService.MediaProjection createMediaProjection()
throws NameNotFoundException {
return createMediaProjection(PACKAGE_NAME);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 0bb4045..eb44daa 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -747,6 +747,23 @@
}
@Test
+ @DisableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+ public void vibrate_singleVibratorComposedAndNoCapability_triggersHalAndReturnsUnsupported() {
+ VibrationEffect effect = VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+ .compose();
+ HalVibration vibration = startThreadAndDispatcher(effect);
+ waitForCompletion();
+
+ verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
+ verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
+ verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+ verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED);
+ assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).isEmpty());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
public void vibrate_singleVibratorComposedAndNoCapability_ignoresVibration() {
VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 5f76d68..ec83e99 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -2865,7 +2865,7 @@
mTestLooper.dispatchAll();
assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
- verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 3}));
+ verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
verify(callback, never()).onStarted(any(IVibrationSession.class));
verify(callback, never()).onFinishing();
verify(callback)
@@ -2889,6 +2889,7 @@
verify(callback).onStarted(captor.capture());
captor.getValue().finishSession();
+ mTestLooper.dispatchAll();
// Session not ended until HAL callback.
assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
@@ -3139,6 +3140,224 @@
}
@Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void vibrateInSession_afterCancel_vibrationIgnored() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1, 2);
+ FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1);
+ fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+ int sessionFinishDelayMs = 200;
+ IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+ mTestLooper.dispatchAll();
+
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+ ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+ verify(callback).onStarted(captor.capture());
+
+ IVibrationSession startedSession = captor.getValue();
+ startedSession.cancelSession();
+ startedSession.vibrate(
+ CombinedVibration.createParallel(VibrationEffect.createOneShot(10, 255)),
+ "reason");
+
+ // VibrationThread will never start this vibration.
+ assertFalse(waitUntil(s -> !fakeVibrator1.getAmplitudes().isEmpty(), service,
+ TEST_TIMEOUT_MILLIS));
+
+ // Dispatch HAL callbacks.
+ mTestLooper.moveTimeForward(sessionFinishDelayMs);
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_USER);
+ verify(callback).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void vibrateInSession_afterFinish_vibrationIgnored() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1, 2);
+ FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1);
+ fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+ int sessionFinishDelayMs = 200;
+ IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+ mTestLooper.dispatchAll();
+
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+ ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+ verify(callback).onStarted(captor.capture());
+
+ IVibrationSession startedSession = captor.getValue();
+ startedSession.finishSession();
+ mTestLooper.dispatchAll();
+
+ startedSession.vibrate(
+ CombinedVibration.createParallel(VibrationEffect.createOneShot(10, 255)),
+ "reason");
+
+ // Session not ended until HAL callback.
+ assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
+
+ // VibrationThread will never start this vibration.
+ assertFalse(waitUntil(s -> !fakeVibrator1.getAmplitudes().isEmpty(), service,
+ TEST_TIMEOUT_MILLIS));
+
+ // Dispatch HAL callbacks.
+ mTestLooper.moveTimeForward(sessionFinishDelayMs);
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.FINISHED);
+ verify(callback).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void vibrateInSession_repeatingVibration_vibrationIgnored() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1, 2);
+ FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1);
+ fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+ int sessionFinishDelayMs = 200;
+ IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+ mTestLooper.dispatchAll();
+
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+ ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+ verify(callback).onStarted(captor.capture());
+
+ IVibrationSession startedSession = captor.getValue();
+ startedSession.vibrate(
+ CombinedVibration.createParallel(
+ VibrationEffect.createWaveform(new long[]{ 10, 10, 10, 10}, 0)),
+ "reason");
+
+ // VibrationThread will never start this vibration.
+ assertFalse(waitUntil(s -> !fakeVibrator1.getAmplitudes().isEmpty(), service,
+ TEST_TIMEOUT_MILLIS));
+
+ startedSession.finishSession();
+ mTestLooper.dispatchAll();
+
+ // Dispatch HAL callbacks.
+ mTestLooper.moveTimeForward(sessionFinishDelayMs);
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.FINISHED);
+ assertThat(service.isVibrating(1)).isFalse();
+ assertThat(service.isVibrating(2)).isFalse();
+ verify(callback).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void vibrateInSession_singleVibration_playsAllVibrateCommands() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1, 2);
+ FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1);
+ fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ FakeVibratorControllerProvider fakeVibrator2 = mVibratorProviders.get(1);
+ fakeVibrator2.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+ int sessionFinishDelayMs = 200;
+ IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+ mTestLooper.dispatchAll();
+
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+ ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+ verify(callback).onStarted(captor.capture());
+
+ IVibrationSession startedSession = captor.getValue();
+ startedSession.vibrate(
+ CombinedVibration.createParallel(
+ VibrationEffect.createWaveform(new long[]{ 10, 10, 10, 10}, -1)),
+ "reason");
+
+ // VibrationThread will start this vibration async, so wait until vibration is triggered.
+ // Vibrators will receive 2 requests for the waveform playback
+ assertTrue(waitUntil(s -> fakeVibrator1.getAmplitudes().size() == 2, service,
+ TEST_TIMEOUT_MILLIS));
+ assertTrue(waitUntil(s -> fakeVibrator2.getAmplitudes().size() == 2, service,
+ TEST_TIMEOUT_MILLIS));
+
+ startedSession.finishSession();
+ mTestLooper.dispatchAll();
+
+ // Dispatch HAL callbacks.
+ mTestLooper.moveTimeForward(sessionFinishDelayMs);
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.FINISHED);
+ assertThat(service.isVibrating(1)).isFalse();
+ assertThat(service.isVibrating(2)).isFalse();
+ verify(callback).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void vibrateInSession_multipleVibrations_playsAllVibrations() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1, 2);
+ FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1);
+ fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+ int sessionFinishDelayMs = 200;
+ IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+ mTestLooper.dispatchAll();
+
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+ ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+ verify(callback).onStarted(captor.capture());
+
+ IVibrationSession startedSession = captor.getValue();
+ startedSession.vibrate(
+ CombinedVibration.createParallel(VibrationEffect.createOneShot(10, 255)),
+ "reason");
+
+ // VibrationThread will start this vibration async, so wait until vibration is completed.
+ assertTrue(waitUntil(s -> fakeVibrator1.getAmplitudes().size() == 1, service,
+ TEST_TIMEOUT_MILLIS));
+ assertTrue(waitUntil(s -> !session.getVibrations().isEmpty(), service,
+ TEST_TIMEOUT_MILLIS));
+
+ startedSession.vibrate(
+ CombinedVibration.createParallel(VibrationEffect.createOneShot(20, 255)),
+ "reason");
+
+ assertTrue(waitUntil(s -> fakeVibrator1.getAmplitudes().size() == 2, service,
+ TEST_TIMEOUT_MILLIS));
+
+ startedSession.finishSession();
+ mTestLooper.dispatchAll();
+
+ // Dispatch HAL callbacks.
+ mTestLooper.moveTimeForward(sessionFinishDelayMs);
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.FINISHED);
+ assertThat(service.isVibrating(1)).isFalse();
+ assertThat(service.isVibrating(2)).isFalse();
+ verify(callback).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+ }
+
+ @Test
public void frameworkStats_externalVibration_reportsAllMetrics() throws Exception {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
diff --git a/services/tests/wmtests/res/xml/bookmarks.xml b/services/tests/wmtests/res/xml/bookmarks.xml
index 197b366..787f4e8 100644
--- a/services/tests/wmtests/res/xml/bookmarks.xml
+++ b/services/tests/wmtests/res/xml/bookmarks.xml
@@ -13,60 +13,70 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<bookmarks>
+<bookmarks xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<!-- the key combinations for the following shortcuts must be in sync
with the key combinations sent by the test in ModifierShortcutTests.java -->
<bookmark
role="android.app.role.BROWSER"
- shortcut="b" />
+ androidprv:keycode="KEYCODE_B"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_CONTACTS"
- shortcut="c" />
+ androidprv:keycode="KEYCODE_C"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_EMAIL"
- shortcut="e" />
+ androidprv:keycode="KEYCODE_E"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_CALENDAR"
- shortcut="k" />
+ androidprv:keycode="KEYCODE_K"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_MAPS"
- shortcut="m" />
+ androidprv:keycode="KEYCODE_M"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_MUSIC"
- shortcut="p" />
+ androidprv:keycode="KEYCODE_P"
+ androidprv:modifierState="META" />
<bookmark
role="android.app.role.SMS"
- shortcut="s" />
+ androidprv:keycode="KEYCODE_S"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_CALCULATOR"
- shortcut="u" />
+ androidprv:keycode="KEYCODE_U"
+ androidprv:modifierState="META" />
<bookmark
role="android.app.role.BROWSER"
- shortcut="b"
- shift="true" />
+ androidprv:keycode="KEYCODE_B"
+ androidprv:modifierState="META|SHIFT" />
<bookmark
category="android.intent.category.APP_CONTACTS"
- shortcut="c"
- shift="true" />
+ androidprv:keycode="KEYCODE_C"
+ androidprv:modifierState="META|SHIFT" />
<bookmark
package="com.test"
class="com.test.BookmarkTest"
- shortcut="j"
- shift="true" />
+ androidprv:keycode="KEYCODE_J"
+ androidprv:modifierState="META|SHIFT" />
<!-- The following shortcuts will not be invoked by tests but are here to
provide test coverage of parsing the different types of shortcut. -->
<bookmark
package="com.test"
class="com.test.BookmarkTest"
- shortcut="j" />
+ androidprv:keycode="KEYCODE_J"
+ androidprv:modifierState="META" />
<bookmark
package="com.test2"
class="com.test.BookmarkTest"
- shortcut="d" />
+ androidprv:keycode="KEYCODE_D"
+ androidprv:modifierState="META" />
<!-- It's intended that this package/class will NOT resolve so we test the resolution
@@ -74,6 +84,7 @@
<bookmark
package="com.test3"
class="com.test.BookmarkTest"
- shortcut="f" />
+ androidprv:keycode="KEYCODE_F"
+ androidprv:modifierState="META" />
</bookmarks>
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
index 0575d98..82a5add 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
@@ -116,6 +116,7 @@
mModifierShortcutManager = new ModifierShortcutManager(
mContext, mHandler, UserHandle.SYSTEM);
+ mModifierShortcutManager.onSystemReady();
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index a51ce995..bc03c23 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -88,6 +88,7 @@
import android.telecom.TelecomManager;
import android.view.Display;
import android.view.InputEvent;
+import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.autofill.AutofillManagerInternal;
@@ -270,11 +271,15 @@
// Return mocked services: LocalServices.getService
mMockitoSession = mockitoSession()
.mockStatic(LocalServices.class, spyStubOnly)
+ .mockStatic(KeyCharacterMap.class)
.strictness(Strictness.LENIENT)
.startMocking();
mPhoneWindowManager = spy(new PhoneWindowManager());
+ KeyCharacterMap virtualKcm = mContext.getSystemService(InputManager.class)
+ .getInputDevice(KeyCharacterMap.VIRTUAL_KEYBOARD).getKeyCharacterMap();
+ doReturn(virtualKcm).when(() -> KeyCharacterMap.load(anyInt()));
doReturn(mWindowManagerInternal).when(
() -> LocalServices.getService(eq(WindowManagerInternal.class)));
doReturn(mActivityManagerInternal).when(
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index b737d35..50e0e18 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -71,6 +71,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -84,12 +85,14 @@
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
+import android.content.ContentResolver;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.Build;
import android.os.IBinder;
import android.os.InputConfig;
import android.os.RemoteException;
@@ -97,6 +100,7 @@
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.provider.Settings;
import android.util.ArraySet;
import android.util.MergedConfiguration;
import android.view.Gravity;
@@ -1557,6 +1561,57 @@
}
@Test
+ public void testIsSecureLocked_flagSecureSet() {
+ WindowState window = createWindow(null /* parent */, TYPE_APPLICATION, "test-window",
+ 1 /* ownerId */);
+ window.mAttrs.flags |= WindowManager.LayoutParams.FLAG_SECURE;
+
+ assertTrue(window.isSecureLocked());
+ }
+
+ @Test
+ public void testIsSecureLocked_flagSecureNotSet() {
+ WindowState window = createWindow(null /* parent */, TYPE_APPLICATION, "test-window",
+ 1 /* ownerId */);
+
+ assertFalse(window.isSecureLocked());
+ }
+
+ @Test
+ public void testIsSecureLocked_disableSecureWindows() {
+ assumeTrue(Build.IS_DEBUGGABLE);
+
+ WindowState window = createWindow(null /* parent */, TYPE_APPLICATION, "test-window",
+ 1 /* ownerId */);
+ window.mAttrs.flags |= WindowManager.LayoutParams.FLAG_SECURE;
+ ContentResolver cr = useFakeSettingsProvider();
+
+ // isSecureLocked should return false when DISABLE_SECURE_WINDOWS is set to 1
+ Settings.Secure.putString(cr, Settings.Secure.DISABLE_SECURE_WINDOWS, "1");
+ mWm.mSettingsObserver.onChange(false /* selfChange */,
+ Settings.Secure.getUriFor(Settings.Secure.DISABLE_SECURE_WINDOWS));
+ assertFalse(window.isSecureLocked());
+
+ // isSecureLocked should return true if DISABLE_SECURE_WINDOWS is set to 0.
+ Settings.Secure.putString(cr, Settings.Secure.DISABLE_SECURE_WINDOWS, "0");
+ mWm.mSettingsObserver.onChange(false /* selfChange */,
+ Settings.Secure.getUriFor(Settings.Secure.DISABLE_SECURE_WINDOWS));
+ assertTrue(window.isSecureLocked());
+
+ // Disable secure windows again.
+ Settings.Secure.putString(cr, Settings.Secure.DISABLE_SECURE_WINDOWS, "1");
+ mWm.mSettingsObserver.onChange(false /* selfChange */,
+ Settings.Secure.getUriFor(Settings.Secure.DISABLE_SECURE_WINDOWS));
+ assertFalse(window.isSecureLocked());
+
+ // isSecureLocked should return true if DISABLE_SECURE_WINDOWS is deleted.
+ Settings.Secure.putString(cr, Settings.Secure.DISABLE_SECURE_WINDOWS, null);
+ mWm.mSettingsObserver.onChange(false /* selfChange */,
+ Settings.Secure.getUriFor(Settings.Secure.DISABLE_SECURE_WINDOWS));
+ assertTrue(window.isSecureLocked());
+ }
+
+ @Test
@RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
public void testIsSecureLocked_sensitiveContentProtectionManagerEnabled() {
String testPackage = "test";
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 7e600b3..2c998c4 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -166,6 +166,25 @@
.waitForAndVerify()
}
+ private fun getHeaderEmptyView(caption: UiObject2?): UiObject2 {
+ return caption
+ ?.children
+ ?.find { it.resourceName.endsWith(HEADER_EMPTY_VIEW) }
+ ?: error("Unable to find resource $HEADER_EMPTY_VIEW\n")
+ }
+
+ /** Click on an existing window's header to bring it to the front. */
+ fun bringToFront(wmHelper: WindowManagerStateHelper, device: UiDevice) {
+ val caption = getCaptionForTheApp(wmHelper, device)
+ val openHeaderView = getHeaderEmptyView(caption)
+ openHeaderView.click()
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withTopVisibleApp(innerHelper)
+ .waitForAndVerify()
+ }
+
/** Open maximize menu and click snap resize button on the app header for the given app. */
fun snapResizeDesktopApp(
wmHelper: WindowManagerStateHelper,
@@ -447,6 +466,7 @@
const val SNAP_LEFT_BUTTON: String = "maximize_menu_snap_left_button"
const val SNAP_RIGHT_BUTTON: String = "maximize_menu_snap_right_button"
const val MINIMIZE_BUTTON_VIEW: String = "minimize_window"
+ const val HEADER_EMPTY_VIEW: String = "caption_handle"
val caption: BySelector
get() = By.res(SYSTEMUI_PACKAGE, CAPTION)
}
diff --git a/tests/Input/res/xml/bookmarks.xml b/tests/Input/res/xml/bookmarks.xml
index ba3f187..a4c898d 100644
--- a/tests/Input/res/xml/bookmarks.xml
+++ b/tests/Input/res/xml/bookmarks.xml
@@ -14,47 +14,55 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<bookmarks>
+<bookmarks xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<!-- the key combinations for the following shortcuts must be in sync
with the key combinations sent by the test in KeyGestureControllerTests.java -->
<bookmark
role="android.app.role.BROWSER"
- shortcut="b" />
+ androidprv:keycode="KEYCODE_B"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_CONTACTS"
- shortcut="c" />
+ androidprv:keycode="KEYCODE_C"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_EMAIL"
- shortcut="e" />
+ androidprv:keycode="KEYCODE_E"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_CALENDAR"
- shortcut="k" />
+ androidprv:keycode="KEYCODE_K"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_MAPS"
- shortcut="m" />
+ androidprv:keycode="KEYCODE_M"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_MUSIC"
- shortcut="p" />
+ androidprv:keycode="KEYCODE_P"
+ androidprv:modifierState="META" />
<bookmark
role="android.app.role.SMS"
- shortcut="s" />
+ androidprv:keycode="KEYCODE_S"
+ androidprv:modifierState="META" />
<bookmark
category="android.intent.category.APP_CALCULATOR"
- shortcut="u" />
+ androidprv:keycode="KEYCODE_U"
+ androidprv:modifierState="META" />
<bookmark
role="android.app.role.BROWSER"
- shortcut="b"
- shift="true" />
+ androidprv:keycode="KEYCODE_B"
+ androidprv:modifierState="META|SHIFT" />
<bookmark
category="android.intent.category.APP_CONTACTS"
- shortcut="c"
+ androidprv:keycode="KEYCODE_C"
shift="true" />
<bookmark
package="com.test"
class="com.test.BookmarkTest"
- shortcut="j"
+ androidprv:keycode="KEYCODE_J"
shift="true" />
</bookmarks>
\ No newline at end of file
diff --git a/tests/Input/res/xml/bookmarks_legacy.xml b/tests/Input/res/xml/bookmarks_legacy.xml
new file mode 100644
index 0000000..8bacf49
--- /dev/null
+++ b/tests/Input/res/xml/bookmarks_legacy.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<bookmarks>
+ <!-- The key combinations for the following shortcuts are legacy way defining bookmarks and we
+ should prefer new way of defining bookmarks as shown in {@link bookmarks.xml} -->
+ <bookmark
+ role="android.app.role.BROWSER"
+ shortcut="b" />
+ <bookmark
+ category="android.intent.category.APP_CONTACTS"
+ shortcut="c" />
+ <bookmark
+ category="android.intent.category.APP_EMAIL"
+ shortcut="e" />
+ <bookmark
+ category="android.intent.category.APP_CALENDAR"
+ shortcut="k" />
+ <bookmark
+ category="android.intent.category.APP_MAPS"
+ shortcut="m" />
+ <bookmark
+ category="android.intent.category.APP_MUSIC"
+ shortcut="p" />
+ <bookmark
+ role="android.app.role.SMS"
+ shortcut="s" />
+ <bookmark
+ category="android.intent.category.APP_CALCULATOR"
+ shortcut="u" />
+
+ <bookmark
+ role="android.app.role.BROWSER"
+ shortcut="b"
+ shift="true" />
+
+ <bookmark
+ category="android.intent.category.APP_CONTACTS"
+ shortcut="c"
+ shift="true" />
+
+ <bookmark
+ package="com.test"
+ class="com.test.BookmarkTest"
+ shortcut="j"
+ shift="true" />
+</bookmarks>
\ No newline at end of file
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 09a686c..d1f8668 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -115,13 +115,11 @@
private lateinit var iInputManager: IInputManager
@Mock
- private lateinit var resources: Resources
-
- @Mock
private lateinit var packageManager: PackageManager
private var currentPid = 0
private lateinit var context: Context
+ private lateinit var resources: Resources
private lateinit var keyGestureController: KeyGestureController
private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
private lateinit var testLooper: TestLooper
@@ -130,6 +128,7 @@
@Before
fun setup() {
context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+ resources = Mockito.spy(context.resources)
setupInputDevices()
setupBehaviors()
testLooper = TestLooper()
@@ -146,10 +145,6 @@
private fun setupBehaviors() {
Mockito.`when`(SystemProperties.get("ro.debuggable")).thenReturn("1")
Mockito.`when`(resources.getBoolean(R.bool.config_enableScreenshotChord)).thenReturn(true)
- val testBookmarks: XmlResourceParser = context.resources.getXml(
- com.android.test.input.R.xml.bookmarks
- )
- Mockito.`when`(resources.getXml(R.xml.bookmarks)).thenReturn(testBookmarks)
Mockito.`when`(context.resources).thenReturn(resources)
Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH))
.thenReturn(true)
@@ -158,6 +153,11 @@
Mockito.`when`(context.packageManager).thenReturn(packageManager)
}
+ private fun setupBookmarks(bookmarkRes: Int) {
+ val testBookmarks: XmlResourceParser = context.resources.getXml(bookmarkRes)
+ Mockito.`when`(resources.getXml(R.xml.bookmarks)).thenReturn(testBookmarks)
+ }
+
private fun setupInputDevices() {
val correctIm = context.getSystemService(InputManager::class.java)!!
val virtualDevice = correctIm.getInputDevice(KeyCharacterMap.VIRTUAL_KEYBOARD)!!
@@ -604,45 +604,6 @@
AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALCULATOR)
),
TestData(
- "META + SHIFT + B -> Launch Default Browser",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_SHIFT_LEFT,
- KeyEvent.KEYCODE_B
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
- intArrayOf(KeyEvent.KEYCODE_B),
- KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
- AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER)
- ),
- TestData(
- "META + SHIFT + C -> Launch Default Contacts",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_SHIFT_LEFT,
- KeyEvent.KEYCODE_C
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
- intArrayOf(KeyEvent.KEYCODE_C),
- KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
- AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS)
- ),
- TestData(
- "META + SHIFT + J -> Launch Target Activity",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_SHIFT_LEFT,
- KeyEvent.KEYCODE_J
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
- intArrayOf(KeyEvent.KEYCODE_J),
- KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
- AppLaunchData.createLaunchDataForComponent("com.test", "com.test.BookmarkTest")
- ),
- TestData(
"META + CTRL + DEL -> Trigger Bug Report",
intArrayOf(
KeyEvent.KEYCODE_META_LEFT,
@@ -866,6 +827,139 @@
}
@Keep
+ private fun bookmarkArguments(): Array<TestData> {
+ return arrayOf(
+ TestData(
+ "META + B -> Launch Default Browser",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_B),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_B),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER)
+ ),
+ TestData(
+ "META + C -> Launch Default Contacts",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_C),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_C),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS)
+ ),
+ TestData(
+ "META + E -> Launch Default Email",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_E),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_E),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_EMAIL)
+ ),
+ TestData(
+ "META + K -> Launch Default Calendar",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_K),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_K),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALENDAR)
+ ),
+ TestData(
+ "META + M -> Launch Default Maps",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_M),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_M),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MAPS)
+ ),
+ TestData(
+ "META + P -> Launch Default Music",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_P),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_P),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MUSIC)
+ ),
+ TestData(
+ "META + S -> Launch Default SMS",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_S),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_S),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_SMS)
+ ),
+ TestData(
+ "META + U -> Launch Default Calculator",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_U),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_U),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALCULATOR)
+ ),
+ TestData(
+ "META + SHIFT + B -> Launch Default Browser",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_B
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_B),
+ KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER)
+ ),
+ TestData(
+ "META + SHIFT + C -> Launch Default Contacts",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_C
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_C),
+ KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS)
+ ),
+ TestData(
+ "META + SHIFT + J -> Launch Target Activity",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_J
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_J),
+ KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForComponent("com.test", "com.test.BookmarkTest")
+ )
+ )
+ }
+
+ @Test
+ @Parameters(method = "bookmarkArguments")
+ fun testBookmarks(test: TestData) {
+ setupBookmarks(com.android.test.input.R.xml.bookmarks)
+ setupKeyGestureController()
+ testKeyGestureInternal(test)
+ }
+
+ @Test
+ @Parameters(method = "bookmarkArguments")
+ fun testBookmarksLegacy(test: TestData) {
+ setupBookmarks(com.android.test.input.R.xml.bookmarks_legacy)
+ setupKeyGestureController()
+ testKeyGestureInternal(test)
+ }
+
+ @Keep
private fun systemKeysTestArguments(): Array<TestData> {
return arrayOf(
TestData(
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java
index 2692e12..44641f7 100644
--- a/tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java
@@ -24,6 +24,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -57,6 +58,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
import perfetto.protos.Protolog;
import perfetto.protos.ProtologCommon;
@@ -858,6 +860,39 @@
.isEqualTo("This message should also be logged 567");
}
+ @Test
+ public void enablesLogGroupAfterLoadingConfig() {
+ sProtoLog.stopLoggingToLogcat(
+ new String[] { TestProtoLogGroup.TEST_GROUP.name() }, (msg) -> {});
+ Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isFalse();
+
+ doAnswer((Answer<Void>) invocation -> {
+ // logToLogcat is still false before we laod the viewer config
+ Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isFalse();
+ return null;
+ }).when(sReader).unloadViewerConfig(any(), any());
+
+ sProtoLog.startLoggingToLogcat(
+ new String[] { TestProtoLogGroup.TEST_GROUP.name() }, (msg) -> {});
+ Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isTrue();
+ }
+
+ @Test
+ public void disablesLogGroupBeforeUnloadingConfig() {
+ sProtoLog.startLoggingToLogcat(
+ new String[] { TestProtoLogGroup.TEST_GROUP.name() }, (msg) -> {});
+ Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isTrue();
+
+ doAnswer((Answer<Void>) invocation -> {
+ // Already set logToLogcat to false by the time we unload the config
+ Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isFalse();
+ return null;
+ }).when(sReader).unloadViewerConfig(any(), any());
+ sProtoLog.stopLoggingToLogcat(
+ new String[] { TestProtoLogGroup.TEST_GROUP.name() }, (msg) -> {});
+ Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isFalse();
+ }
+
private enum TestProtoLogGroup implements IProtoLogGroup {
TEST_GROUP(true, true, false, "TEST_TAG");
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index f68ae2c..74a907f 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -309,7 +309,7 @@
*/
@TestApi
@NonNull
- @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api")
+ @SuppressLint("UnflaggedApi") // Exempt: Test API for already shipped feature
public BroadcastReceiver getBroadcastReceiver() {
return mBroadcastReceiver;
}