Merge "Add Centering animation for large clock" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 8591a9c..6ecd38f 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -21,6 +21,7 @@
// !!! KEEP THIS LIST ALPHABETICAL !!!
"aconfig_mediacodec_flags_java_lib",
"android.adaptiveauth.flags-aconfig-java",
+ "android.app.contextualsearch.flags-aconfig-java",
"android.app.flags-aconfig-java",
"android.app.ondeviceintelligence-aconfig-java",
"android.app.smartspace.flags-aconfig-java",
@@ -974,6 +975,19 @@
],
}
+// Contextual Search
+aconfig_declarations {
+ name: "android.app.contextualsearch.flags-aconfig",
+ package: "android.app.contextualsearch.flags",
+ srcs: ["core/java/android/app/contextualsearch/flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.app.contextualsearch.flags-aconfig-java",
+ aconfig_declarations: "android.app.contextualsearch.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Smartspace
aconfig_declarations {
name: "android.app.smartspace.flags-aconfig",
diff --git a/core/api/current.txt b/core/api/current.txt
index 173cf6c..cc3f52f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6849,7 +6849,7 @@
}
public abstract static class Notification.Style {
- ctor public Notification.Style();
+ ctor @Deprecated public Notification.Style();
method public android.app.Notification build();
method protected void checkBuilder();
method protected android.widget.RemoteViews getStandardView(int);
@@ -12487,33 +12487,33 @@
}
public class LauncherApps {
- method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(String, android.os.UserHandle);
- method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getAllPackageInstallerSessions();
+ method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(String, android.os.UserHandle);
+ method @NonNull @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getAllPackageInstallerSessions();
method @FlaggedApi("android.os.allow_private_profile") @Nullable @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public android.content.IntentSender getAppMarketActivityIntent(@Nullable String, @NonNull android.os.UserHandle);
- method public android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
method @FlaggedApi("android.os.allow_private_profile") @Nullable @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public final android.content.pm.LauncherUserInfo getLauncherUserInfo(@NonNull android.os.UserHandle);
method public android.content.pm.LauncherApps.PinItemRequest getPinItemRequest(android.content.Intent);
method @FlaggedApi("android.os.allow_private_profile") @NonNull @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public java.util.List<java.lang.String> getPreInstalledSystemPackages(@NonNull android.os.UserHandle);
- method public java.util.List<android.os.UserHandle> getProfiles();
+ method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public java.util.List<android.os.UserHandle> getProfiles();
method public android.graphics.drawable.Drawable getShortcutBadgedIconDrawable(android.content.pm.ShortcutInfo, int);
method @Nullable public android.content.IntentSender getShortcutConfigActivityIntent(@NonNull android.content.pm.LauncherActivityInfo);
method public java.util.List<android.content.pm.LauncherActivityInfo> getShortcutConfigActivityList(@Nullable String, @NonNull android.os.UserHandle);
method public android.graphics.drawable.Drawable getShortcutIconDrawable(@NonNull android.content.pm.ShortcutInfo, int);
method @Nullable public android.app.PendingIntent getShortcutIntent(@NonNull String, @NonNull String, @Nullable android.os.Bundle, @NonNull android.os.UserHandle);
method @Nullable public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(@NonNull android.content.pm.LauncherApps.ShortcutQuery, @NonNull android.os.UserHandle);
- method @Nullable public android.os.Bundle getSuspendedPackageLauncherExtras(String, android.os.UserHandle);
+ method @Nullable @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public android.os.Bundle getSuspendedPackageLauncherExtras(String, android.os.UserHandle);
method public boolean hasShortcutHostPermission();
- method public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle);
- method public boolean isPackageEnabled(String, android.os.UserHandle);
+ method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle);
+ method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public boolean isPackageEnabled(String, android.os.UserHandle);
method public void pinShortcuts(@NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull android.os.UserHandle);
- method public void registerCallback(android.content.pm.LauncherApps.Callback);
- method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler);
+ method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public void registerCallback(android.content.pm.LauncherApps.Callback);
+ method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler);
method public void registerPackageInstallerSessionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.content.pm.PackageInstaller.SessionCallback);
- method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle);
+ method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle);
method @FlaggedApi("android.content.pm.archiving") public void setArchiveCompatibility(@NonNull android.content.pm.LauncherApps.ArchiveCompatibilityParams);
- method public boolean shouldHideFromSuggestions(@NonNull String, @NonNull android.os.UserHandle);
- method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
- method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
+ method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public boolean shouldHideFromSuggestions(@NonNull String, @NonNull android.os.UserHandle);
+ method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
+ method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
method public void startPackageInstallerSessionDetailsActivity(@NonNull android.content.pm.PackageInstaller.SessionInfo, @Nullable android.graphics.Rect, @Nullable android.os.Bundle);
method public void startShortcut(@NonNull String, @NonNull String, @Nullable android.graphics.Rect, @Nullable android.os.Bundle, @NonNull android.os.UserHandle);
method public void startShortcut(@NonNull android.content.pm.ShortcutInfo, @Nullable android.graphics.Rect, @Nullable android.os.Bundle);
@@ -20121,9 +20121,14 @@
ctor public OutputConfiguration(@NonNull android.view.Surface);
ctor public OutputConfiguration(int, @NonNull android.view.Surface);
ctor public <T> OutputConfiguration(@NonNull android.util.Size, @NonNull Class<T>);
+ ctor @FlaggedApi("com.android.internal.camera.flags.camera_device_setup") public OutputConfiguration(int, @NonNull android.util.Size);
+ ctor @FlaggedApi("com.android.internal.camera.flags.camera_device_setup") public OutputConfiguration(int, int, @NonNull android.util.Size);
+ ctor @FlaggedApi("com.android.internal.camera.flags.camera_device_setup") public OutputConfiguration(int, @NonNull android.util.Size, long);
+ ctor @FlaggedApi("com.android.internal.camera.flags.camera_device_setup") public OutputConfiguration(int, int, @NonNull android.util.Size, long);
method public void addSensorPixelModeUsed(int);
method public void addSurface(@NonNull android.view.Surface);
method @NonNull public static java.util.Collection<android.hardware.camera2.params.OutputConfiguration> createInstancesForMultiResolutionOutput(@NonNull android.hardware.camera2.MultiResolutionImageReader);
+ method @FlaggedApi("com.android.internal.camera.flags.camera_device_setup") @NonNull public static java.util.List<android.hardware.camera2.params.OutputConfiguration> createInstancesForMultiResolutionOutput(@NonNull java.util.Collection<android.hardware.camera2.params.MultiResolutionStreamInfo>, int);
method public int describeContents();
method public void enableSurfaceSharing();
method public long getDynamicRangeProfile();
@@ -20142,6 +20147,7 @@
method public void setPhysicalCameraId(@Nullable String);
method public void setReadoutTimestampEnabled(boolean);
method public void setStreamUseCase(long);
+ method @FlaggedApi("com.android.internal.camera.flags.camera_device_setup") public static void setSurfacesForMultiResolutionOutput(@NonNull java.util.Collection<android.hardware.camera2.params.OutputConfiguration>, @NonNull android.hardware.camera2.MultiResolutionImageReader);
method public void setTimestampBase(int);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR;
@@ -20203,6 +20209,7 @@
public final class SessionConfiguration implements android.os.Parcelable {
ctor public SessionConfiguration(int, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback);
+ ctor @FlaggedApi("com.android.internal.camera.flags.camera_device_setup") public SessionConfiguration(int, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>);
method public void clearColorSpace();
method public int describeContents();
method @Nullable public android.graphics.ColorSpace getColorSpace();
@@ -20212,6 +20219,7 @@
method public android.hardware.camera2.CaptureRequest getSessionParameters();
method public int getSessionType();
method public android.hardware.camera2.CameraCaptureSession.StateCallback getStateCallback();
+ method @FlaggedApi("com.android.internal.camera.flags.camera_device_setup") public void setCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback);
method public void setColorSpace(@NonNull android.graphics.ColorSpace.Named);
method public void setInputConfiguration(@NonNull android.hardware.camera2.params.InputConfiguration);
method public void setSessionParameters(android.hardware.camera2.CaptureRequest);
@@ -33960,6 +33968,7 @@
method public static boolean supportsMultipleUsers();
field public static final String ALLOW_PARENT_PROFILE_APP_LINKING = "allow_parent_profile_app_linking";
field @Deprecated public static final String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile";
+ field @FlaggedApi("android.os.allow_private_profile") public static final String DISALLOW_ADD_PRIVATE_PROFILE = "no_add_private_profile";
field public static final String DISALLOW_ADD_USER = "no_add_user";
field public static final String DISALLOW_ADD_WIFI_CONFIG = "no_add_wifi_config";
field public static final String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
@@ -52193,7 +52202,7 @@
method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") @NonNull public android.view.SurfaceControl.Transaction addTransactionCompletedListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.SurfaceControl.TransactionStats>);
method public void apply();
method @NonNull public android.view.SurfaceControl.Transaction clearFrameRate(@NonNull android.view.SurfaceControl);
- method @NonNull public android.view.SurfaceControl.Transaction clearTrustedPresentationCallback(@NonNull android.view.SurfaceControl);
+ method @Deprecated @NonNull public android.view.SurfaceControl.Transaction clearTrustedPresentationCallback(@NonNull android.view.SurfaceControl);
method public void close();
method public int describeContents();
method @NonNull public android.view.SurfaceControl.Transaction merge(@NonNull android.view.SurfaceControl.Transaction);
@@ -52218,7 +52227,7 @@
method @NonNull public android.view.SurfaceControl.Transaction setOpaque(@NonNull android.view.SurfaceControl, boolean);
method @NonNull public android.view.SurfaceControl.Transaction setPosition(@NonNull android.view.SurfaceControl, float, float);
method @NonNull public android.view.SurfaceControl.Transaction setScale(@NonNull android.view.SurfaceControl, float, float);
- method @NonNull public android.view.SurfaceControl.Transaction setTrustedPresentationCallback(@NonNull android.view.SurfaceControl, @NonNull android.view.SurfaceControl.TrustedPresentationThresholds, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @Deprecated @NonNull public android.view.SurfaceControl.Transaction setTrustedPresentationCallback(@NonNull android.view.SurfaceControl, @NonNull android.view.SurfaceControl.TrustedPresentationThresholds, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @NonNull public android.view.SurfaceControl.Transaction setVisibility(@NonNull android.view.SurfaceControl, boolean);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.view.SurfaceControl.Transaction> CREATOR;
@@ -52233,8 +52242,8 @@
method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") @NonNull public android.hardware.SyncFence getPresentFence();
}
- public static final class SurfaceControl.TrustedPresentationThresholds {
- ctor public SurfaceControl.TrustedPresentationThresholds(@FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @IntRange(from=1) int);
+ @Deprecated public static final class SurfaceControl.TrustedPresentationThresholds {
+ ctor @Deprecated public SurfaceControl.TrustedPresentationThresholds(@FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @IntRange(from=1) int);
}
@FlaggedApi("com.android.window.flags.surface_control_input_receiver") public interface SurfaceControlInputReceiver {
@@ -52249,7 +52258,7 @@
method public void relayout(int, int);
method public void release();
method public void setView(@NonNull android.view.View, int, int);
- method public boolean transferTouchGestureToHost();
+ method @Deprecated public boolean transferTouchGestureToHost();
}
public static final class SurfaceControlViewHost.SurfacePackage implements android.os.Parcelable {
@@ -52308,7 +52317,7 @@
ctor public SurfaceView(android.content.Context, android.util.AttributeSet, int, int);
method public void applyTransactionToFrame(@NonNull android.view.SurfaceControl.Transaction);
method public android.view.SurfaceHolder getHolder();
- method @Nullable public android.os.IBinder getHostToken();
+ method @Deprecated @Nullable public android.os.IBinder getHostToken();
method public android.view.SurfaceControl getSurfaceControl();
method public void setChildSurfacePackage(@NonNull android.view.SurfaceControlViewHost.SurfacePackage);
method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public void setDesiredHdrHeadroom(@FloatRange(from=0.0f, to=10000.0) float);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 4d3e252..9039bf1 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -7,6 +7,7 @@
field public static final String ACCESS_BROADCAST_RADIO = "android.permission.ACCESS_BROADCAST_RADIO";
field public static final String ACCESS_BROADCAST_RESPONSE_STATS = "android.permission.ACCESS_BROADCAST_RESPONSE_STATS";
field public static final String ACCESS_CACHE_FILESYSTEM = "android.permission.ACCESS_CACHE_FILESYSTEM";
+ field @FlaggedApi("android.app.contextualsearch.flags.enable_service") public static final String ACCESS_CONTEXTUAL_SEARCH = "android.permission.ACCESS_CONTEXTUAL_SEARCH";
field public static final String ACCESS_CONTEXT_HUB = "android.permission.ACCESS_CONTEXT_HUB";
field public static final String ACCESS_DRM_CERTIFICATES = "android.permission.ACCESS_DRM_CERTIFICATES";
field @Deprecated public static final String ACCESS_FM_RADIO = "android.permission.ACCESS_FM_RADIO";
@@ -198,7 +199,6 @@
field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
field public static final String MANAGE_DEVICE_POLICY_APP_EXEMPTIONS = "android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS";
field @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") public static final String MANAGE_DEVICE_POLICY_AUDIT_LOGGING = "android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING";
- field @FlaggedApi("android.app.admin.flags.device_theft_api_enabled") public static final String MANAGE_DEVICE_POLICY_THEFT_DETECTION = "android.permission.MANAGE_DEVICE_POLICY_THEFT_DETECTION";
field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String MANAGE_ENHANCED_CONFIRMATION_STATES = "android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES";
field public static final String MANAGE_ETHERNET_NETWORKS = "android.permission.MANAGE_ETHERNET_NETWORKS";
field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION";
@@ -282,6 +282,7 @@
field @FlaggedApi("android.content.pm.quarantined_enabled") public static final String QUARANTINE_APPS = "android.permission.QUARANTINE_APPS";
field public static final String QUERY_ADMIN_POLICY = "android.permission.QUERY_ADMIN_POLICY";
field public static final String QUERY_CLONED_APPS = "android.permission.QUERY_CLONED_APPS";
+ field @FlaggedApi("android.app.admin.flags.device_theft_api_enabled") public static final String QUERY_DEVICE_STOLEN_STATE = "android.permission.QUERY_DEVICE_STOLEN_STATE";
field @Deprecated public static final String QUERY_TIME_ZONE_RULES = "android.permission.QUERY_TIME_ZONE_RULES";
field public static final String QUERY_USERS = "android.permission.QUERY_USERS";
field public static final String RADIO_SCAN_WITHOUT_LOCATION = "android.permission.RADIO_SCAN_WITHOUT_LOCATION";
@@ -1328,12 +1329,12 @@
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public int getUserProvisioningState();
method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public boolean isAuditLogEnabled();
method public boolean isDeviceManaged();
+ method @FlaggedApi("android.app.admin.flags.device_theft_api_enabled") @RequiresPermission(android.Manifest.permission.QUERY_DEVICE_STOLEN_STATE) public boolean isDevicePotentiallyStolen();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioningConfigApplied();
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean isDpcDownloaded();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public boolean isManagedKiosk();
method public boolean isSecondaryLockscreenEnabled(@NonNull android.os.UserHandle);
- method @FlaggedApi("android.app.admin.flags.device_theft_api_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_THEFT_DETECTION) public boolean isTheftDetectionTriggered();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public boolean isUnattendedManagedKiosk();
method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long);
method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long, boolean);
@@ -2176,6 +2177,39 @@
}
+package android.app.contextualsearch {
+
+ @FlaggedApi("android.app.contextualsearch.flags.enable_service") public class ContextualSearchManager {
+ method public void getContextualSearchState(@NonNull android.os.IBinder, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.contextualsearch.ContextualSearchState,java.lang.Throwable>);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXTUAL_SEARCH) public void startContextualSearch(int);
+ field public static final String ACTION_LAUNCH_CONTEXTUAL_SEARCH = "android.app.contextualsearch.action.LAUNCH_CONTEXTUAL_SEARCH";
+ field public static final int ENTRYPOINT_LONG_PRESS_HOME = 2; // 0x2
+ field public static final int ENTRYPOINT_LONG_PRESS_META = 10; // 0xa
+ field public static final int ENTRYPOINT_LONG_PRESS_NAV_HANDLE = 1; // 0x1
+ field public static final int ENTRYPOINT_LONG_PRESS_OVERVIEW = 3; // 0x3
+ field public static final int ENTRYPOINT_OVERVIEW_ACTION = 4; // 0x4
+ field public static final int ENTRYPOINT_OVERVIEW_MENU = 5; // 0x5
+ field public static final int ENTRYPOINT_SYSTEM_ACTION = 9; // 0x9
+ field public static final String EXTRA_ENTRYPOINT = "android.app.contextualsearch.extra.ENTRYPOINT";
+ field public static final String EXTRA_FLAG_SECURE_FOUND = "android.app.contextualsearch.extra.FLAG_SECURE_FOUND";
+ field public static final String EXTRA_IS_MANAGED_PROFILE_VISIBLE = "android.app.contextualsearch.extra.IS_MANAGED_PROFILE_VISIBLE";
+ field public static final String EXTRA_SCREENSHOT = "android.app.contextualsearch.extra.SCREENSHOT";
+ field public static final String EXTRA_TOKEN = "android.app.contextualsearch.extra.TOKEN";
+ field public static final String EXTRA_VISIBLE_PACKAGE_NAMES = "android.app.contextualsearch.extra.VISIBLE_PACKAGE_NAMES";
+ }
+
+ @FlaggedApi("android.app.contextualsearch.flags.enable_service") public final class ContextualSearchState implements android.os.Parcelable {
+ ctor public ContextualSearchState(@Nullable android.app.assist.AssistStructure, @Nullable android.app.assist.AssistContent, @NonNull android.os.Bundle);
+ method public int describeContents();
+ method @Nullable public android.app.assist.AssistContent getContent();
+ method @NonNull public android.os.Bundle getExtras();
+ method @Nullable public android.app.assist.AssistStructure getStructure();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.contextualsearch.ContextualSearchState> CREATOR;
+ }
+
+}
+
package android.app.job {
public abstract class JobScheduler {
@@ -2190,14 +2224,6 @@
package android.app.ondeviceintelligence {
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class Content implements android.os.Parcelable {
- ctor public Content(@NonNull android.os.Bundle);
- method public int describeContents();
- method @NonNull public android.os.Bundle getData();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.Content> CREATOR;
- }
-
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface DownloadCallback {
method public void onDownloadCompleted(@NonNull android.os.PersistableBundle);
method public void onDownloadFailed(int, @Nullable String, @NonNull android.os.PersistableBundle);
@@ -2233,11 +2259,11 @@
}
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class FeatureDetails implements android.os.Parcelable {
- ctor public FeatureDetails(@android.app.ondeviceintelligence.FeatureDetails.Status int, @NonNull android.os.PersistableBundle);
- ctor public FeatureDetails(@android.app.ondeviceintelligence.FeatureDetails.Status int);
+ ctor public FeatureDetails(int, @NonNull android.os.PersistableBundle);
+ ctor public FeatureDetails(int);
method public int describeContents();
method @NonNull public android.os.PersistableBundle getFeatureDetailParams();
- method @android.app.ondeviceintelligence.FeatureDetails.Status public int getFeatureStatus();
+ method public int getFeatureStatus();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FeatureDetails> CREATOR;
field public static final int FEATURE_STATUS_AVAILABLE = 3; // 0x3
@@ -2247,35 +2273,14 @@
field public static final int FEATURE_STATUS_UNAVAILABLE = 0; // 0x0
}
- @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD}) public static @interface FeatureDetails.Status {
- }
-
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public class OnDeviceIntelligenceManager {
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeature(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method @Nullable @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public String getRemoteServicePackageName();
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getVersion(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.LongConsumer);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void listFeatures(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.ProcessingOutcomeReceiver);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestFeatureDownload(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.DownloadCallback);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenInfo(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- field public static final int REQUEST_TYPE_EMBEDDINGS = 2; // 0x2
- field public static final int REQUEST_TYPE_INFERENCE = 0; // 0x0
- field public static final int REQUEST_TYPE_PREPARE = 1; // 0x1
- }
-
- public static class OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException extends java.lang.Exception {
- ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException(int, @NonNull String, @NonNull android.os.PersistableBundle);
- ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException(int, @NonNull android.os.PersistableBundle);
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public class OnDeviceIntelligenceException extends java.lang.Exception {
+ ctor public OnDeviceIntelligenceException(int, @NonNull String, @NonNull android.os.PersistableBundle);
+ ctor public OnDeviceIntelligenceException(int, @NonNull android.os.PersistableBundle);
+ ctor public OnDeviceIntelligenceException(int, @NonNull String);
+ ctor public OnDeviceIntelligenceException(int);
method public int getErrorCode();
method @NonNull public android.os.PersistableBundle getErrorParams();
- field public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 1000; // 0x3e8
- }
-
- public static class OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException extends android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException {
- ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException(int, @NonNull String, @NonNull android.os.PersistableBundle);
- ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException(int, @NonNull android.os.PersistableBundle);
+ field public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 100; // 0x64
field public static final int PROCESSING_ERROR_BAD_DATA = 2; // 0x2
field public static final int PROCESSING_ERROR_BAD_REQUEST = 3; // 0x3
field public static final int PROCESSING_ERROR_BUSY = 9; // 0x9
@@ -2291,10 +2296,28 @@
field public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15; // 0xf
field public static final int PROCESSING_ERROR_SUSPENDED = 13; // 0xd
field public static final int PROCESSING_ERROR_UNKNOWN = 1; // 0x1
+ field public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 200; // 0xc8
}
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface ProcessingOutcomeReceiver extends android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> {
- method public default void onDataAugmentRequest(@NonNull android.app.ondeviceintelligence.Content, @NonNull java.util.function.Consumer<android.app.ondeviceintelligence.Content>);
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class OnDeviceIntelligenceManager {
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeature(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ method @Nullable @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public String getRemoteServicePackageName();
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getVersion(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.LongConsumer);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void listFeatures(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.ProcessingCallback);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamingProcessingCallback);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestFeatureDownload(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.DownloadCallback);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenInfo(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ field public static final int REQUEST_TYPE_EMBEDDINGS = 2; // 0x2
+ field public static final int REQUEST_TYPE_INFERENCE = 0; // 0x0
+ field public static final int REQUEST_TYPE_PREPARE = 1; // 0x1
+ }
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface ProcessingCallback {
+ method public default void onDataAugmentRequest(@NonNull android.os.Bundle, @NonNull java.util.function.Consumer<android.os.Bundle>);
+ method public void onError(@NonNull android.app.ondeviceintelligence.OnDeviceIntelligenceException);
+ method public void onResult(@NonNull android.os.Bundle);
}
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class ProcessingSignal {
@@ -2307,8 +2330,8 @@
method public void onSignalReceived(@NonNull android.os.PersistableBundle);
}
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamedProcessingOutcomeReceiver extends android.app.ondeviceintelligence.ProcessingOutcomeReceiver {
- method public void onNewContent(@NonNull android.app.ondeviceintelligence.Content);
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamingProcessingCallback extends android.app.ondeviceintelligence.ProcessingCallback {
+ method public void onPartialResult(@NonNull android.os.Bundle);
}
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class TokenInfo implements android.os.Parcelable {
@@ -3338,7 +3361,7 @@
method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @Nullable public static android.app.wearable.WearableSensingDataRequest getDataRequestFromIntent(@NonNull android.content.Intent);
method @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideConnection(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideData(@NonNull android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
- method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideDataStream(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideDataStream(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void registerDataRequestObserver(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("android.app.wearable.enable_hotword_wearable_sensing_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void startHotwordRecognition(@Nullable android.content.ComponentName, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("android.app.wearable.enable_hotword_wearable_sensing_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void stopHotwordRecognition(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
@@ -3758,6 +3781,7 @@
field public static final String CLOUDSEARCH_SERVICE = "cloudsearch";
field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
field public static final String CONTEXTHUB_SERVICE = "contexthub";
+ field @FlaggedApi("android.app.contextualsearch.flags.enable_service") public static final String CONTEXTUAL_SEARCH_SERVICE = "contextual_search";
field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String ECM_ENHANCED_CONFIRMATION_SERVICE = "ecm_enhanced_confirmation";
field public static final String ETHERNET_SERVICE = "ethernet";
field public static final String EUICC_CARD_SERVICE = "euicc_card";
@@ -5017,7 +5041,7 @@
}
public static interface DeviceStateManager.DeviceStateCallback {
- method public default void onDeviceStateChanged(@NonNull android.hardware.devicestate.DeviceState);
+ method public void onDeviceStateChanged(@NonNull android.hardware.devicestate.DeviceState);
method public default void onSupportedStatesChanged(@NonNull java.util.List<android.hardware.devicestate.DeviceState>);
}
@@ -6203,8 +6227,6 @@
method public long getNanoAppId();
method public boolean isBroadcastMessage();
method @FlaggedApi("android.chre.flags.reliable_message") public boolean isReliable();
- method @FlaggedApi("android.chre.flags.reliable_message") public void setIsReliable(boolean);
- method @FlaggedApi("android.chre.flags.reliable_message") public void setMessageSequenceNumber(int);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppMessage> CREATOR;
}
@@ -12956,38 +12978,26 @@
ctor public OnDeviceIntelligenceService();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public abstract void onDownloadFeature(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull android.app.ondeviceintelligence.DownloadCallback);
- method public abstract void onGetFeature(int, int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method public abstract void onGetFeatureDetails(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method public abstract void onGetFeature(int, int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ method public abstract void onGetFeatureDetails(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
method public abstract void onGetReadOnlyFeatureFileDescriptorMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>>);
method public abstract void onGetVersion(@NonNull java.util.function.LongConsumer);
method public abstract void onInferenceServiceConnected();
method public abstract void onInferenceServiceDisconnected();
- method public abstract void onListFeatures(int, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method public final void updateProcessingState(@NonNull android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>);
+ method public abstract void onListFeatures(int, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ method public final void updateProcessingState(@NonNull android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
}
- public abstract static class OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException extends java.lang.Exception {
- ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int);
- ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int, @NonNull String);
- method public int getErrorCode();
- }
-
- public static class OnDeviceIntelligenceService.OnDeviceUpdateProcessingException extends android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException {
- ctor public OnDeviceIntelligenceService.OnDeviceUpdateProcessingException(int);
- ctor public OnDeviceIntelligenceService.OnDeviceUpdateProcessingException(int, @NonNull String);
- field public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 1; // 0x1
- }
-
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceSandboxedInferenceService extends android.app.Service {
ctor public OnDeviceSandboxedInferenceService();
method public final void fetchFeatureFileInputStreamMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.io.FileInputStream>>);
method @NonNull public java.util.concurrent.Executor getCallbackExecutor();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
- method @NonNull public abstract void onProcessRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.ProcessingOutcomeReceiver);
- method @NonNull public abstract void onProcessRequestStreaming(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver);
- method @NonNull public abstract void onTokenInfoRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
- method public abstract void onUpdateProcessingState(@NonNull android.os.Bundle, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>);
+ method @NonNull public abstract void onProcessRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.ProcessingCallback);
+ method @NonNull public abstract void onProcessRequestStreaming(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamingProcessingCallback);
+ method @NonNull public abstract void onTokenInfoRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ method public abstract void onUpdateProcessingState(@NonNull android.os.Bundle, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
method public final java.io.FileInputStream openFileInput(@NonNull String) throws java.io.FileNotFoundException;
method public final void openFileInputAsync(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.io.FileInputStream>) throws java.io.FileNotFoundException;
field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService";
@@ -13004,11 +13014,11 @@
method public long getMaximumDataBlockSize();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public boolean getOemUnlockEnabled();
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_PDB_STATE) public String getPersistentDataPackageName();
- method public byte[] read();
+ method @Nullable public byte[] read();
method @FlaggedApi("android.security.frp_enforcement") public boolean setFactoryResetProtectionSecret(@NonNull byte[]);
method @Deprecated @RequiresPermission("android.permission.OEM_UNLOCK_STATE") public void setOemUnlockEnabled(boolean);
method @RequiresPermission("android.permission.OEM_UNLOCK_STATE") public void wipe();
- method public int write(byte[]);
+ method public int write(@Nullable byte[]);
field public static final int FLASH_LOCK_LOCKED = 1; // 0x1
field public static final int FLASH_LOCK_UNKNOWN = -1; // 0xffffffff
field public static final int FLASH_LOCK_UNLOCKED = 0; // 0x0
@@ -13961,9 +13971,8 @@
}
@FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final class DisconnectCause.Builder {
- ctor public DisconnectCause.Builder();
+ ctor public DisconnectCause.Builder(int);
method @NonNull public android.telecom.DisconnectCause build();
- method @NonNull public android.telecom.DisconnectCause.Builder setCode(int);
method @NonNull public android.telecom.DisconnectCause.Builder setDescription(@Nullable CharSequence);
method @NonNull public android.telecom.DisconnectCause.Builder setImsReasonInfo(@Nullable android.telephony.ims.ImsReasonInfo);
method @NonNull public android.telecom.DisconnectCause.Builder setLabel(@Nullable CharSequence);
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 1e72a06..62fc67b 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -1885,8 +1885,6 @@
Documentation mentions 'TODO'
Todo: android.app.NotificationManager#isNotificationAssistantAccessGranted(android.content.ComponentName):
Documentation mentions 'TODO'
-Todo: android.app.ondeviceintelligence.OnDeviceIntelligenceManager#requestFeatureDownload(android.app.ondeviceintelligence.Feature, android.app.ondeviceintelligence.CancellationSignal, java.util.concurrent.Executor, android.app.ondeviceintelligence.DownloadCallback):
- Documentation mentions 'TODO'
Todo: android.hardware.camera2.params.StreamConfigurationMap:
Documentation mentions 'TODO'
Todo: android.hardware.location.ContextHubManager#getNanoAppInstanceInfo(int):
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index bc45a76..c1af719 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1625,25 +1625,37 @@
package android.hardware.devicestate {
@FlaggedApi("android.hardware.devicestate.feature.flags.device_state_property_api") public final class DeviceState {
+ ctor public DeviceState(@NonNull android.hardware.devicestate.DeviceState.Configuration);
field public static final int PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST = 8; // 0x8
}
+ public static final class DeviceState.Configuration implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getIdentifier();
+ method @NonNull public String getName();
+ method @NonNull public java.util.Set<java.lang.Integer> getPhysicalProperties();
+ method @NonNull public java.util.Set<java.lang.Integer> getSystemProperties();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.devicestate.DeviceState.Configuration> CREATOR;
+ }
+
+ public static final class DeviceState.Configuration.Builder {
+ ctor public DeviceState.Configuration.Builder(int, @NonNull String);
+ method @NonNull public android.hardware.devicestate.DeviceState.Configuration build();
+ method @NonNull public android.hardware.devicestate.DeviceState.Configuration.Builder setPhysicalProperties(@NonNull java.util.Set<java.lang.Integer>);
+ method @NonNull public android.hardware.devicestate.DeviceState.Configuration.Builder setSystemProperties(@NonNull java.util.Set<java.lang.Integer>);
+ }
+
@FlaggedApi("android.hardware.devicestate.feature.flags.device_state_property_api") public final class DeviceStateManager {
method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void cancelBaseStateOverride();
method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void cancelStateRequest();
- method @Deprecated @NonNull public int[] getSupportedStates();
method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void requestBaseStateOverride(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
+ field public static final int INVALID_DEVICE_STATE_IDENTIFIER = -1; // 0xffffffff
field public static final int MAXIMUM_DEVICE_STATE_IDENTIFIER = 10000; // 0x2710
field public static final int MINIMUM_DEVICE_STATE_IDENTIFIER = 0; // 0x0
}
- public static interface DeviceStateManager.DeviceStateCallback {
- method @Deprecated public default void onBaseStateChanged(int);
- method @Deprecated public void onStateChanged(int);
- method @Deprecated public default void onSupportedStatesChanged(@NonNull int[]);
- }
-
public final class DeviceStateRequest {
method public int getFlags();
method public int getState();
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 2afc78c..a8352fa 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2951,7 +2951,9 @@
new AppOpInfo.Builder(OP_ESTABLISH_VPN_MANAGER, OPSTR_ESTABLISH_VPN_MANAGER,
"ESTABLISH_VPN_MANAGER").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
new AppOpInfo.Builder(OP_ACCESS_RESTRICTED_SETTINGS, OPSTR_ACCESS_RESTRICTED_SETTINGS,
- "ACCESS_RESTRICTED_SETTINGS").setDefaultMode(AppOpsManager.MODE_ALLOWED)
+ "ACCESS_RESTRICTED_SETTINGS").setDefaultMode(
+ android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
+ ? MODE_DEFAULT : MODE_ALLOWED)
.setDisableReset(true).setRestrictRead(true).build(),
new AppOpInfo.Builder(OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO,
"RECEIVE_SOUNDTRIGGER_AUDIO").setDefaultMode(AppOpsManager.MODE_ALLOWED)
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index ed0c933..6f6e091 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -274,11 +274,9 @@
private Context mOuterContext;
private final Object mThemeLock = new Object();
-
@UnsupportedAppUsage
@GuardedBy("mThemeLock")
private int mThemeResource = 0;
-
@UnsupportedAppUsage
@GuardedBy("mThemeLock")
private Resources.Theme mTheme = null;
@@ -347,17 +345,21 @@
private boolean mOwnsToken = false;
private final Object mDirsLock = new Object();
- private volatile File mDatabasesDir;
- @UnsupportedAppUsage private volatile File mPreferencesDir;
- private volatile File mFilesDir;
- private volatile File mCratesDir;
- private volatile File mNoBackupFilesDir;
- private volatile File[] mExternalFilesDirs;
- private volatile File[] mObbDirs;
- private volatile File mCacheDir;
- private volatile File mCodeCacheDir;
- private volatile File[] mExternalCacheDirs;
- private volatile File[] mExternalMediaDirs;
+ @GuardedBy("mDirsLock")
+ private File mDatabasesDir;
+ @GuardedBy("mDirsLock")
+ @UnsupportedAppUsage
+ private File mPreferencesDir;
+ @GuardedBy("mDirsLock")
+ private File mFilesDir;
+ @GuardedBy("mDirsLock")
+ private File mCratesDir;
+ @GuardedBy("mDirsLock")
+ private File mNoBackupFilesDir;
+ @GuardedBy("mDirsLock")
+ private File mCacheDir;
+ @GuardedBy("mDirsLock")
+ private File mCodeCacheDir;
// The system service cache for the system services that are cached per-ContextImpl.
@UnsupportedAppUsage
@@ -491,7 +493,6 @@
}
}
- @GuardedBy("mThemeLock")
private void initializeTheme() {
if (mTheme == null) {
mTheme = mResources.newTheme();
@@ -741,18 +742,12 @@
@UnsupportedAppUsage
private File getPreferencesDir() {
- File localPreferencesDir = mPreferencesDir;
- if (localPreferencesDir == null) {
- synchronized (mDirsLock) {
- localPreferencesDir = mPreferencesDir;
- if (localPreferencesDir == null) {
- localPreferencesDir = new File(getDataDir(), "shared_prefs");
- ensurePrivateDirExists(localPreferencesDir);
- mPreferencesDir = localPreferencesDir;
- }
+ synchronized (mDirsLock) {
+ if (mPreferencesDir == null) {
+ mPreferencesDir = new File(getDataDir(), "shared_prefs");
}
+ return ensurePrivateDirExists(mPreferencesDir);
}
- return localPreferencesDir;
}
@Override
@@ -794,16 +789,16 @@
/**
* Common-path handling of app data dir creation
*/
- private static void ensurePrivateDirExists(File file) {
- ensurePrivateDirExists(file, 0771, -1, null);
+ private static File ensurePrivateDirExists(File file) {
+ return ensurePrivateDirExists(file, 0771, -1, null);
}
- private static void ensurePrivateCacheDirExists(File file, String xattr) {
+ private static File ensurePrivateCacheDirExists(File file, String xattr) {
final int gid = UserHandle.getCacheAppGid(Process.myUid());
- ensurePrivateDirExists(file, 02771, gid, xattr);
+ return ensurePrivateDirExists(file, 02771, gid, xattr);
}
- private static void ensurePrivateDirExists(File file, int mode, int gid, String xattr) {
+ private static File ensurePrivateDirExists(File file, int mode, int gid, String xattr) {
if (!file.exists()) {
final String path = file.getAbsolutePath();
try {
@@ -831,22 +826,17 @@
}
}
}
+ return file;
}
@Override
public File getFilesDir() {
- File localFilesDir = mFilesDir;
- if (localFilesDir == null) {
- localFilesDir = mFilesDir;
- synchronized (mDirsLock) {
- if (localFilesDir == null) {
- localFilesDir = new File(getDataDir(), "files");
- ensurePrivateDirExists(localFilesDir);
- mFilesDir = localFilesDir;
- }
+ synchronized (mDirsLock) {
+ if (mFilesDir == null) {
+ mFilesDir = new File(getDataDir(), "files");
}
+ return ensurePrivateDirExists(mFilesDir);
}
- return localFilesDir;
}
@Override
@@ -856,37 +846,25 @@
final Path absoluteNormalizedCratePath = cratesRootPath.resolve(crateId)
.toAbsolutePath().normalize();
- File localCratesDir = mCratesDir;
- if (localCratesDir == null) {
- synchronized (mDirsLock) {
- localCratesDir = mCratesDir;
- if (localCratesDir == null) {
- localCratesDir = cratesRootPath.toFile();
- ensurePrivateDirExists(localCratesDir);
- mCratesDir = localCratesDir;
- }
+ synchronized (mDirsLock) {
+ if (mCratesDir == null) {
+ mCratesDir = cratesRootPath.toFile();
}
+ ensurePrivateDirExists(mCratesDir);
}
- File crateDir = absoluteNormalizedCratePath.toFile();
- ensurePrivateDirExists(crateDir);
- return crateDir;
+ File cratedDir = absoluteNormalizedCratePath.toFile();
+ return ensurePrivateDirExists(cratedDir);
}
@Override
public File getNoBackupFilesDir() {
- File localNoBackupFilesDir = mNoBackupFilesDir;
- if (localNoBackupFilesDir == null) {
- synchronized (mDirsLock) {
- localNoBackupFilesDir = mNoBackupFilesDir;
- if (localNoBackupFilesDir == null) {
- localNoBackupFilesDir = new File(getDataDir(), "no_backup");
- ensurePrivateDirExists(localNoBackupFilesDir);
- mNoBackupFilesDir = localNoBackupFilesDir;
- }
+ synchronized (mDirsLock) {
+ if (mNoBackupFilesDir == null) {
+ mNoBackupFilesDir = new File(getDataDir(), "no_backup");
}
+ return ensurePrivateDirExists(mNoBackupFilesDir);
}
- return localNoBackupFilesDir;
}
@Override
@@ -898,24 +876,13 @@
@Override
public File[] getExternalFilesDirs(String type) {
- File[] localExternalFilesDirs = mExternalFilesDirs;
- if (localExternalFilesDirs == null) {
- synchronized (mDirsLock) {
- localExternalFilesDirs = mExternalFilesDirs;
- if (localExternalFilesDirs == null) {
- localExternalFilesDirs =
- Environment.buildExternalStorageAppFilesDirs(getPackageName());
- if (type != null) {
- localExternalFilesDirs =
- Environment.buildPaths(localExternalFilesDirs, type);
- }
- localExternalFilesDirs = ensureExternalDirsExistOrFilter(
- localExternalFilesDirs, true /* tryCreateInProcess */);
- mExternalFilesDirs = localExternalFilesDirs;
- }
+ synchronized (mDirsLock) {
+ File[] dirs = Environment.buildExternalStorageAppFilesDirs(getPackageName());
+ if (type != null) {
+ dirs = Environment.buildPaths(dirs, type);
}
+ return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
}
- return localExternalFilesDirs;
}
@Override
@@ -927,51 +894,30 @@
@Override
public File[] getObbDirs() {
- File[] localObbDirs = mObbDirs;
- if (mObbDirs == null) {
- synchronized (mDirsLock) {
- localObbDirs = mObbDirs;
- if (localObbDirs == null) {
- localObbDirs = Environment.buildExternalStorageAppObbDirs(getPackageName());
- localObbDirs = ensureExternalDirsExistOrFilter(
- localObbDirs, true /* tryCreateInProcess */);
- mObbDirs = localObbDirs;
- }
- }
+ synchronized (mDirsLock) {
+ File[] dirs = Environment.buildExternalStorageAppObbDirs(getPackageName());
+ return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
}
- return localObbDirs;
}
@Override
public File getCacheDir() {
- File localCacheDir = mCacheDir;
- if (localCacheDir == null) {
- synchronized (mDirsLock) {
- localCacheDir = mCacheDir;
- if (localCacheDir == null) {
- localCacheDir = new File(getDataDir(), "cache");
- ensurePrivateCacheDirExists(localCacheDir, XATTR_INODE_CACHE);
- mCacheDir = localCacheDir;
- }
+ synchronized (mDirsLock) {
+ if (mCacheDir == null) {
+ mCacheDir = new File(getDataDir(), "cache");
}
+ return ensurePrivateCacheDirExists(mCacheDir, XATTR_INODE_CACHE);
}
- return localCacheDir;
}
@Override
public File getCodeCacheDir() {
- File localCodeCacheDir = mCodeCacheDir;
- if (localCodeCacheDir == null) {
- synchronized (mDirsLock) {
- localCodeCacheDir = mCodeCacheDir;
- if (localCodeCacheDir == null) {
- localCodeCacheDir = getCodeCacheDirBeforeBind(getDataDir());
- ensurePrivateCacheDirExists(localCodeCacheDir, XATTR_INODE_CODE_CACHE);
- mCodeCacheDir = localCodeCacheDir;
- }
+ synchronized (mDirsLock) {
+ if (mCodeCacheDir == null) {
+ mCodeCacheDir = getCodeCacheDirBeforeBind(getDataDir());
}
+ return ensurePrivateCacheDirExists(mCodeCacheDir, XATTR_INODE_CODE_CACHE);
}
- return localCodeCacheDir;
}
/**
@@ -992,37 +938,21 @@
@Override
public File[] getExternalCacheDirs() {
- File[] localExternalCacheDirs = mExternalCacheDirs;
- if (localExternalCacheDirs == null) {
- synchronized (mDirsLock) {
- localExternalCacheDirs = mExternalCacheDirs;
- if (localExternalCacheDirs == null) {
- localExternalCacheDirs =
- Environment.buildExternalStorageAppCacheDirs(getPackageName());
- localExternalCacheDirs = ensureExternalDirsExistOrFilter(
- localExternalCacheDirs, false /* tryCreateInProcess */);
- mExternalCacheDirs = localExternalCacheDirs;
- }
- }
+ synchronized (mDirsLock) {
+ File[] dirs = Environment.buildExternalStorageAppCacheDirs(getPackageName());
+ // We don't try to create cache directories in-process, because they need special
+ // setup for accurate quota tracking. This ensures the cache dirs are always
+ // created through StorageManagerService.
+ return ensureExternalDirsExistOrFilter(dirs, false /* tryCreateInProcess */);
}
- return localExternalCacheDirs;
}
@Override
public File[] getExternalMediaDirs() {
- File[] localExternalMediaDirs = mExternalMediaDirs;
- if (localExternalMediaDirs == null) {
- synchronized (mDirsLock) {
- localExternalMediaDirs = mExternalMediaDirs;
- if (localExternalMediaDirs == null) {
- localExternalMediaDirs = Environment.buildExternalStorageAppMediaDirs(getPackageName());
- localExternalMediaDirs = ensureExternalDirsExistOrFilter(
- localExternalMediaDirs, true /* tryCreateInProcess */);
- mExternalMediaDirs = localExternalMediaDirs;
- }
- }
+ synchronized (mDirsLock) {
+ File[] dirs = Environment.buildExternalStorageAppMediaDirs(getPackageName());
+ return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
}
- return localExternalMediaDirs;
}
/**
@@ -1121,22 +1051,16 @@
}
private File getDatabasesDir() {
- File localDatabasesDir = mDatabasesDir;
- if (localDatabasesDir == null) {
- synchronized (mDirsLock) {
- localDatabasesDir = mDatabasesDir;
- if (localDatabasesDir == null) {
- if ("android".equals(getPackageName())) {
- localDatabasesDir = new File("/data/system");
- } else {
- localDatabasesDir = new File(getDataDir(), "databases");
- }
- ensurePrivateDirExists(localDatabasesDir);
- mDatabasesDir = localDatabasesDir;
+ synchronized (mDirsLock) {
+ if (mDatabasesDir == null) {
+ if ("android".equals(getPackageName())) {
+ mDatabasesDir = new File("/data/system");
+ } else {
+ mDatabasesDir = new File(getDataDir(), "databases");
}
}
+ return ensurePrivateDirExists(mDatabasesDir);
}
- return localDatabasesDir;
}
@Override
diff --git a/core/java/android/app/ForegroundServiceDelegationOptions.java b/core/java/android/app/ForegroundServiceDelegationOptions.java
index 875e01f..d6b6a58 100644
--- a/core/java/android/app/ForegroundServiceDelegationOptions.java
+++ b/core/java/android/app/ForegroundServiceDelegationOptions.java
@@ -92,6 +92,16 @@
*/
public final @DelegationService int mDelegationService;
+ /**
+ * The optional notification Id of the foreground service delegation.
+ */
+ public final int mClientNotificationId;
+
+ /**
+ * The optional notification of the foreground service delegation.
+ */
+ public final @Nullable Notification mClientNotification;
+
public ForegroundServiceDelegationOptions(int clientPid,
int clientUid,
@NonNull String clientPackageName,
@@ -100,6 +110,21 @@
@NonNull String clientInstanceName,
int foregroundServiceTypes,
@DelegationService int delegationService) {
+ this(clientPid, clientUid, clientPackageName, clientAppThread, isSticky,
+ clientInstanceName, foregroundServiceTypes, delegationService,
+ 0 /* notificationId */, null /* notification */);
+ }
+
+ public ForegroundServiceDelegationOptions(int clientPid,
+ int clientUid,
+ @NonNull String clientPackageName,
+ @NonNull IApplicationThread clientAppThread,
+ boolean isSticky,
+ @NonNull String clientInstanceName,
+ int foregroundServiceTypes,
+ @DelegationService int delegationService,
+ int clientNotificationId,
+ @Nullable Notification clientNotification) {
mClientPid = clientPid;
mClientUid = clientUid;
mClientPackageName = clientPackageName;
@@ -108,6 +133,8 @@
mClientInstanceName = clientInstanceName;
mForegroundServiceTypes = foregroundServiceTypes;
mDelegationService = delegationService;
+ mClientNotificationId = clientNotificationId;
+ mClientNotification = clientNotification;
}
/**
@@ -201,7 +228,8 @@
int mClientPid; // The actual app PID
int mClientUid; // The actual app UID
String mClientPackageName; // The actual app's package name
- int mClientNotificationId; // The actual app's notification
+ int mClientNotificationId; // The actual app's notification id
+ Notification mClientNotification; // The actual app's notification
IApplicationThread mClientAppThread; // The actual app's app thread
boolean mSticky; // Is it a sticky service
String mClientInstanceName; // The delegation service instance name
@@ -233,10 +261,12 @@
}
/**
- * Set the notification ID from the client app.
+ * Set the notification from the client app.
*/
- public Builder setClientNotificationId(int clientNotificationId) {
+ public Builder setClientNotification(int clientNotificationId,
+ @Nullable Notification clientNotification) {
mClientNotificationId = clientNotificationId;
+ mClientNotification = clientNotification;
return this;
}
@@ -291,7 +321,9 @@
mSticky,
mClientInstanceName,
mForegroundServiceTypes,
- mDelegationService
+ mDelegationService,
+ mClientNotificationId,
+ mClientNotification
);
}
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index cd4c0bc..7337a7c 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3586,12 +3586,15 @@
* Sets the token used for background operations for the pending intents associated with this
* notification.
*
+ * This token is automatically set during deserialization for you, you usually won't need to
+ * call this unless you want to change the existing token, if any.
+ *
* @hide
*/
- public void overrideAllowlistToken(IBinder token) {
- mAllowlistToken = token;
+ public void clearAllowlistToken() {
+ mAllowlistToken = null;
if (publicVersion != null) {
- publicVersion.overrideAllowlistToken(token);
+ publicVersion.clearAllowlistToken();
}
}
@@ -7379,6 +7382,15 @@
public static abstract class Style {
/**
+ * @deprecated public access to the constructor of Style() is only useful for creating
+ * custom subclasses, but that has actually been impossible due to hidden abstract
+ * methods, so this constructor is now officially deprecated to clarify that this is
+ * intended to be disallowed.
+ */
+ @Deprecated
+ public Style() {}
+
+ /**
* The number of items allowed simulatanously in the remote input history.
* @hide
*/
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 9f2e473..7c803eb 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -776,7 +776,8 @@
/**
* Whether or not notifications posted to this channel can bypass the Do Not Disturb
- * {@link NotificationManager#INTERRUPTION_FILTER_PRIORITY} mode.
+ * {@link NotificationManager#INTERRUPTION_FILTER_PRIORITY} mode when the active policy allows
+ * priority channels to bypass notification filtering.
*/
public boolean canBypassDnd() {
return mBypassDnd;
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index d01626e..66269a5 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -32,8 +32,11 @@
import android.app.blob.BlobStoreManagerFrameworkInitializer;
import android.app.contentsuggestions.ContentSuggestionsManager;
import android.app.contentsuggestions.IContentSuggestionsManager;
+import android.app.contextualsearch.ContextualSearchManager;
import android.app.ecm.EnhancedConfirmationFrameworkInitializer;
import android.app.job.JobSchedulerFrameworkInitializer;
+import android.app.ondeviceintelligence.IOnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
import android.app.people.PeopleManager;
import android.app.prediction.AppPredictionManager;
import android.app.role.RoleFrameworkInitializer;
@@ -1280,6 +1283,16 @@
}
});
+ registerService(Context.CONTEXTUAL_SEARCH_SERVICE, ContextualSearchManager.class,
+ new CachedServiceFetcher<>() {
+ @Override
+ public ContextualSearchManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getService(Context.CONTEXTUAL_SEARCH_SERVICE);
+ return b == null ? null : new ContextualSearchManager();
+ }
+ });
+
registerService(Context.APP_PREDICTION_SERVICE, AppPredictionManager.class,
new CachedServiceFetcher<AppPredictionManager>() {
@Override
@@ -1589,6 +1602,19 @@
return new WearableSensingManager(ctx.getOuterContext(), manager);
}});
+ registerService(Context.ON_DEVICE_INTELLIGENCE_SERVICE, OnDeviceIntelligenceManager.class,
+ new CachedServiceFetcher<OnDeviceIntelligenceManager>() {
+ @Override
+ public OnDeviceIntelligenceManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder iBinder = ServiceManager.getServiceOrThrow(
+ Context.ON_DEVICE_INTELLIGENCE_SERVICE);
+ IOnDeviceIntelligenceManager manager =
+ IOnDeviceIntelligenceManager.Stub.asInterface(iBinder);
+ return new OnDeviceIntelligenceManager(ctx.getOuterContext(), manager);
+ }
+ });
+
registerService(Context.GRAMMATICAL_INFLECTION_SERVICE, GrammaticalInflectionManager.class,
new CachedServiceFetcher<GrammaticalInflectionManager>() {
@Override
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index cb4ed058..5b28f50 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -45,11 +45,11 @@
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_STATUS_BAR;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SYSTEM_UPDATES;
-import static android.Manifest.permission.MANAGE_DEVICE_POLICY_THEFT_DETECTION;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIFI;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIPE_DATA;
import static android.Manifest.permission.QUERY_ADMIN_POLICY;
+import static android.Manifest.permission.QUERY_DEVICE_STOLEN_STATE;
import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY;
import static android.Manifest.permission.SET_TIME;
import static android.Manifest.permission.SET_TIME_ZONE;
@@ -5952,7 +5952,8 @@
* <p>
* This method can be called on the {@link DevicePolicyManager} instance returned by
* {@link #getParentProfileInstance(ComponentName)} in order to set a value on the parent
- * profile.
+ * profile. This allows a profile wipe after too many incorrect device-unlock password have
+ * been entered on the parent profile even if each profile has a separate challenge.
* <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
* password is always empty and this method has no effect - i.e. the policy is not set.
*
@@ -9979,17 +9980,22 @@
/**
* Called by a profile owner or device owner or holder of the permission
* {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_TASK}. to set a default activity
- * that the system selects to handle intents that match the given {@link IntentFilter}.
+ * that the system selects to handle intents that match the given {@link IntentFilter} instead
+ * of showing the default disambiguation mechanism.
* This activity will remain the default intent handler even if the set of potential event
* handlers for the intent filter changes and if the intent preferences are reset.
* <p>
- * Note that the caller should still declare the activity in the manifest, the API just sets
- * the activity to be the default one to handle the given intent filter.
+ * Note that the target application should still declare the activity in the manifest, the API
+ * just sets the activity to be the default one to handle the given intent filter.
* <p>
* The default disambiguation mechanism takes over if the activity is not installed (anymore).
* When the activity is (re)installed, it is automatically reset as default intent handler for
* the filter.
* <p>
+ * Note that calling this API to set a default intent handler, only allow to avoid the default
+ * disambiguation mechanism. Implicit intents that do not trigger this mechanism (like invoking
+ * the browser) cannot be configured as they are controlled by other configurations.
+ * <p>
* The calling device admin must be a profile owner or device owner. If it is not, a security
* exception will be thrown.
* <p>
@@ -17152,15 +17158,15 @@
* @hide
*/
@SystemApi
- @RequiresPermission(value = MANAGE_DEVICE_POLICY_THEFT_DETECTION)
+ @RequiresPermission(value = QUERY_DEVICE_STOLEN_STATE)
@FlaggedApi(FLAG_DEVICE_THEFT_API_ENABLED)
- public boolean isTheftDetectionTriggered() {
- throwIfParentInstance("isTheftDetectionTriggered");
+ public boolean isDevicePotentiallyStolen() {
+ throwIfParentInstance("isDevicePotentiallyStolen");
if (mService == null) {
return false;
}
try {
- return mService.isTheftDetectionTriggered(mContext.getPackageName());
+ return mService.isDevicePotentiallyStolen(mContext.getPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 03d0b0f..aea0246 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -581,7 +581,7 @@
void setWifiSsidPolicy(String callerPackageName, in WifiSsidPolicy policy);
WifiSsidPolicy getWifiSsidPolicy(String callerPackageName);
- boolean isTheftDetectionTriggered(String callerPackageName);
+ boolean isDevicePotentiallyStolen(String callerPackageName);
List<UserHandle> listForegroundAffiliatedUsers();
void setDrawables(in List<DevicePolicyDrawableResource> drawables);
diff --git a/core/java/android/app/contextualsearch/ContextualSearchManager.java b/core/java/android/app/contextualsearch/ContextualSearchManager.java
new file mode 100644
index 0000000..693de21
--- /dev/null
+++ b/core/java/android/app/contextualsearch/ContextualSearchManager.java
@@ -0,0 +1,230 @@
+/*
+ * 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.app.contextualsearch;
+
+import static android.Manifest.permission.ACCESS_CONTEXTUAL_SEARCH;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.app.contextualsearch.flags.Flags;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.OutcomeReceiver;
+import android.os.ParcelableException;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * {@link ContextualSearchManager} is a system service to facilitate contextual search experience on
+ * configured Android devices.
+ * <p>
+ * This class lets
+ * <ul>
+ * <li> a caller start contextual search by calling {@link #startContextualSearch} method.
+ * <li> a handler request {@link ContextualSearchState} by calling the
+ * {@link #getContextualSearchState} method.
+ * </ul>
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_ENABLE_SERVICE)
+public class ContextualSearchManager {
+
+ /**
+ * Key to get the entrypoint from the extras of the activity launched by contextual search.
+ * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+ */
+ public static final String EXTRA_ENTRYPOINT =
+ "android.app.contextualsearch.extra.ENTRYPOINT";
+ /**
+ * Key to get the flag_secure value from the extras of the activity launched by contextual
+ * search. The value will be true if flag_secure is found in any of the visible activities.
+ * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+ */
+ public static final String EXTRA_FLAG_SECURE_FOUND =
+ "android.app.contextualsearch.extra.FLAG_SECURE_FOUND";
+ /**
+ * Key to get the screenshot from the extras of the activity launched by contextual search.
+ * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+ */
+ public static final String EXTRA_SCREENSHOT =
+ "android.app.contextualsearch.extra.SCREENSHOT";
+ /**
+ * Key to check whether managed profile is visible from the extras of the activity launched by
+ * contextual search. The value will be true if any one of the visible apps is managed.
+ * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+ */
+ public static final String EXTRA_IS_MANAGED_PROFILE_VISIBLE =
+ "android.app.contextualsearch.extra.IS_MANAGED_PROFILE_VISIBLE";
+ /**
+ * Key to get the list of visible packages from the extras of the activity launched by
+ * contextual search.
+ * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+ */
+ public static final String EXTRA_VISIBLE_PACKAGE_NAMES =
+ "android.app.contextualsearch.extra.VISIBLE_PACKAGE_NAMES";
+
+ /**
+ * Key to get the binder token from the extras of the activity launched by contextual search.
+ * This token is needed to invoke {@link #getContextualSearchState} method.
+ * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+ */
+ public static final String EXTRA_TOKEN = "android.app.contextualsearch.extra.TOKEN";
+ /**
+ * Intent action for contextual search invocation. The app providing the contextual search
+ * experience must add this intent filter action to the activity it wants to be launched.
+ * <br>
+ * <b>Note</b> This activity must not be exported.
+ */
+ public static final String ACTION_LAUNCH_CONTEXTUAL_SEARCH =
+ "android.app.contextualsearch.action.LAUNCH_CONTEXTUAL_SEARCH";
+
+ /** Entrypoint to be used when a user long presses on the nav handle. */
+ public static final int ENTRYPOINT_LONG_PRESS_NAV_HANDLE = 1;
+ /** Entrypoint to be used when a user long presses on the home button. */
+ public static final int ENTRYPOINT_LONG_PRESS_HOME = 2;
+ /** Entrypoint to be used when a user long presses on the overview button. */
+ public static final int ENTRYPOINT_LONG_PRESS_OVERVIEW = 3;
+ /** Entrypoint to be used when a user presses the action button in overview. */
+ public static final int ENTRYPOINT_OVERVIEW_ACTION = 4;
+ /** Entrypoint to be used when a user presses the context menu button in overview. */
+ public static final int ENTRYPOINT_OVERVIEW_MENU = 5;
+ /** Entrypoint to be used by system actions like TalkBack, Accessibility etc. */
+ public static final int ENTRYPOINT_SYSTEM_ACTION = 9;
+ /** Entrypoint to be used when a user long presses on the meta key. */
+ public static final int ENTRYPOINT_LONG_PRESS_META = 10;
+ /**
+ * The {@link Entrypoint} annotation is used to standardize the entrypoints supported by
+ * {@link #startContextualSearch} method.
+ *
+ * @hide
+ */
+ @IntDef(prefix = {"ENTRYPOINT_"}, value = {
+ ENTRYPOINT_LONG_PRESS_NAV_HANDLE,
+ ENTRYPOINT_LONG_PRESS_HOME,
+ ENTRYPOINT_LONG_PRESS_OVERVIEW,
+ ENTRYPOINT_OVERVIEW_ACTION,
+ ENTRYPOINT_OVERVIEW_MENU,
+ ENTRYPOINT_SYSTEM_ACTION,
+ ENTRYPOINT_LONG_PRESS_META
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Entrypoint {
+ }
+ private static final String TAG = ContextualSearchManager.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private final IContextualSearchManager mService;
+
+ /** @hide */
+ public ContextualSearchManager() {
+ if (DEBUG) Log.d(TAG, "ContextualSearchManager created");
+ IBinder b = ServiceManager.getService(Context.CONTEXTUAL_SEARCH_SERVICE);
+ mService = IContextualSearchManager.Stub.asInterface(b);
+ }
+
+ /**
+ * Used to start contextual search.
+ * <p>
+ * When {@link #startContextualSearch} is called, the system server does the following:
+ * <ul>
+ * <li>Resolves the activity using the package name and intent filter. The package name
+ * is fetched from the config specified in ContextualSearchManagerService.
+ * The activity must have ACTION_LAUNCH_CONTEXTUAL_SEARCH specified in its manifest.
+ * <li>Puts the required extras in the launch intent.
+ * <li>Launches the activity.
+ * </ul>
+ * </p>
+ *
+ * @param entrypoint the invocation entrypoint
+ */
+ @RequiresPermission(ACCESS_CONTEXTUAL_SEARCH)
+ public void startContextualSearch(@Entrypoint int entrypoint) {
+ if (DEBUG) Log.d(TAG, "startContextualSearch for entrypoint: " + entrypoint);
+ try {
+ mService.startContextualSearch(entrypoint);
+ } catch (RemoteException e) {
+ if (DEBUG) Log.d(TAG, "Failed to startContextualSearch", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the {@link ContextualSearchState} to the handler via the provided callback.
+ *
+ * @param token The caller is expected to get the token from the launch extras of the handling
+ * activity using {@link Bundle#getIBinder} with {@link #EXTRA_TOKEN} key.
+ * <br>
+ * <b>Note</b> This token is for one time use only. Subsequent uses will invoke
+ * callback's {@link OutcomeReceiver#onError}.
+ * @param executor The executor which will be used to invoke the callback.
+ * @param callback The callback which will be used to return {@link ContextualSearchState}
+ * if/when it is available via {@link OutcomeReceiver#onResult}. It will also be
+ * used to return errors via {@link OutcomeReceiver#onError}.
+ */
+ public void getContextualSearchState(@NonNull IBinder token,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<ContextualSearchState, Throwable> callback) {
+ if (DEBUG) Log.d(TAG, "getContextualSearchState for token:" + token);
+ try {
+ final CallbackWrapper wrapper = new CallbackWrapper(executor, callback);
+ mService.getContextualSearchState(token, wrapper);
+ } catch (RemoteException e) {
+ if (DEBUG) Log.d(TAG, "Failed to getContextualSearchState", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ private static class CallbackWrapper extends IContextualSearchCallback.Stub {
+ private final OutcomeReceiver<ContextualSearchState, Throwable> mCallback;
+ private final Executor mExecutor;
+
+ CallbackWrapper(@NonNull Executor callbackExecutor,
+ @NonNull OutcomeReceiver<ContextualSearchState, Throwable> callback) {
+ mCallback = callback;
+ mExecutor = callbackExecutor;
+ }
+
+ @Override
+ public void onResult(ContextualSearchState state) {
+ Binder.withCleanCallingIdentity(() -> {
+ if (DEBUG) Log.d(TAG, "onResult state:" + state);
+ mExecutor.execute(() -> mCallback.onResult(state));
+ });
+ }
+
+ @Override
+ public void onError(ParcelableException error) {
+ Binder.withCleanCallingIdentity(() -> {
+ if (DEBUG) Log.w(TAG, "onError", error);
+ mExecutor.execute(() -> mCallback.onError(error));
+ });
+ }
+ }
+}
diff --git a/core/java/android/app/ondeviceintelligence/Content.aidl b/core/java/android/app/contextualsearch/ContextualSearchState.aidl
similarity index 80%
rename from core/java/android/app/ondeviceintelligence/Content.aidl
rename to core/java/android/app/contextualsearch/ContextualSearchState.aidl
index 40f0ef9..7f64484 100644
--- a/core/java/android/app/ondeviceintelligence/Content.aidl
+++ b/core/java/android/app/contextualsearch/ContextualSearchState.aidl
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2024, The Android Open Source Project
+ * Copyright (c) 2021, 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.
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-package android.app.ondeviceintelligence;
+package android.app.contextualsearch;
-/**
- * @hide
- */
-parcelable Content;
+parcelable ContextualSearchState;
\ No newline at end of file
diff --git a/core/java/android/app/contextualsearch/ContextualSearchState.java b/core/java/android/app/contextualsearch/ContextualSearchState.java
new file mode 100644
index 0000000..5c04bc89
--- /dev/null
+++ b/core/java/android/app/contextualsearch/ContextualSearchState.java
@@ -0,0 +1,104 @@
+/*
+ * 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.app.contextualsearch;
+
+import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.assist.AssistContent;
+import android.app.assist.AssistStructure;
+import android.app.contextualsearch.flags.Flags;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+/**
+ * {@link ContextualSearchState} contains additional data a contextual search handler can request
+ * via {@link ContextualSearchManager#getContextualSearchState} method.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_ENABLE_SERVICE)
+@SystemApi
+public final class ContextualSearchState implements Parcelable {
+ private final @NonNull Bundle mExtras;
+ private final @Nullable AssistStructure mStructure;
+ private final @Nullable AssistContent mContent;
+
+ public ContextualSearchState(@Nullable AssistStructure structure,
+ @Nullable AssistContent content, @NonNull Bundle extras) {
+ mStructure = structure;
+ mContent = content;
+ mExtras = extras;
+ }
+
+ private ContextualSearchState(Parcel source) {
+ this.mStructure = source.readTypedObject(AssistStructure.CREATOR);
+ this.mContent = source.readTypedObject(AssistContent.CREATOR);
+ Bundle extras = source.readBundle(getClass().getClassLoader());
+ this.mExtras = extras != null ? extras : Bundle.EMPTY;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeTypedObject(this.mStructure, flags);
+ dest.writeTypedObject(this.mContent, flags);
+ dest.writeBundle(this.mExtras);
+ }
+
+ /** Gets an instance of {@link AssistContent}. */
+ @Nullable
+ public AssistContent getContent() {
+ return mContent;
+ }
+
+ /** Gets an instance of {@link AssistStructure}. */
+ @Nullable
+ public AssistStructure getStructure() {
+ return mStructure;
+ }
+
+ /**
+ * Gets an instance of {@link Bundle} containing the extras added by the system server.
+ * The contents of this bundle vary by usecase. When Contextual is invoked via Launcher, this
+ * bundle is empty.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @NonNull
+ public static final Creator<ContextualSearchState> CREATOR = new Creator<>() {
+ @Override
+ public ContextualSearchState createFromParcel(Parcel source) {
+ return new ContextualSearchState(source);
+ }
+
+ @Override
+ public ContextualSearchState[] newArray(int size) {
+ return new ContextualSearchState[size];
+ }
+ };
+}
diff --git a/core/java/android/app/contextualsearch/IContextualSearchCallback.aidl b/core/java/android/app/contextualsearch/IContextualSearchCallback.aidl
new file mode 100644
index 0000000..5fe5fd2
--- /dev/null
+++ b/core/java/android/app/contextualsearch/IContextualSearchCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 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.app.contextualsearch;
+
+import android.os.ParcelableException;
+import android.app.contextualsearch.ContextualSearchState;
+/**
+ * @hide
+ */
+oneway interface IContextualSearchCallback {
+ void onResult(in ContextualSearchState state);
+ void onError(in ParcelableException error);
+}
diff --git a/core/java/android/app/contextualsearch/IContextualSearchManager.aidl b/core/java/android/app/contextualsearch/IContextualSearchManager.aidl
new file mode 100644
index 0000000..1735a71
--- /dev/null
+++ b/core/java/android/app/contextualsearch/IContextualSearchManager.aidl
@@ -0,0 +1,11 @@
+package android.app.contextualsearch;
+
+
+import android.app.contextualsearch.IContextualSearchCallback;
+/**
+ * @hide
+ */
+oneway interface IContextualSearchManager {
+ void startContextualSearch(int entrypoint);
+ void getContextualSearchState(in IBinder token, in IContextualSearchCallback callback);
+}
diff --git a/core/java/android/app/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig
new file mode 100644
index 0000000..5ab0762
--- /dev/null
+++ b/core/java/android/app/contextualsearch/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.app.contextualsearch.flags"
+
+flag {
+ name: "enable_service"
+ namespace: "machine_learning"
+ description: "Flag to enable the service"
+ bug: "309689654"
+}
diff --git a/core/java/android/app/ondeviceintelligence/Content.java b/core/java/android/app/ondeviceintelligence/Content.java
deleted file mode 100644
index 51bd156..0000000
--- a/core/java/android/app/ondeviceintelligence/Content.java
+++ /dev/null
@@ -1,90 +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 android.app.ondeviceintelligence;
-
-import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
-
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-import android.annotation.SystemApi;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.Objects;
-
-/**
- * Represents content sent to and received from the on-device inference service.
- * Can contain a collection of text, image, and binary parts or any combination of these.
- *
- * @hide
- */
-@SystemApi
-@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public final class Content implements Parcelable {
- //TODO: Improve javadoc after adding validation logic.
- private static final String TAG = "Content";
- private final Bundle mData;
-
- /**
- * Create a content object using a Bundle of only known types that are read-only.
- */
- public Content(@NonNull Bundle data) {
- Objects.requireNonNull(data);
- validateBundleData(data);
- this.mData = data;
- }
-
- /**
- * Returns the Content's data represented as a Bundle.
- */
- @NonNull
- public Bundle getData() {
- return mData;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeBundle(mData);
- }
-
- @Override
- public int describeContents() {
- int mask = 0;
- mask |= mData.describeContents();
- return mask;
- }
-
- @NonNull
- public static final Creator<Content> CREATOR = new Creator<>() {
- @Override
- @NonNull
- public Content createFromParcel(@NonNull Parcel in) {
- return new Content(in.readBundle(getClass().getClassLoader()));
- }
-
- @Override
- @NonNull
- public Content[] newArray(int size) {
- return new Content[size];
- }
- };
-
- private void validateBundleData(Bundle unused) {
- // TODO: Validate there are only known types.
- }
-}
diff --git a/core/java/android/app/ondeviceintelligence/DownloadCallback.java b/core/java/android/app/ondeviceintelligence/DownloadCallback.java
index 684c71f..30c6e19 100644
--- a/core/java/android/app/ondeviceintelligence/DownloadCallback.java
+++ b/core/java/android/app/ondeviceintelligence/DownloadCallback.java
@@ -105,7 +105,7 @@
}
/**
- * Called when model download via MDD completed. The remote implementation can populate any
+ * Called when model download is completed. The remote implementation can populate any
* associated download params like file stats etc. in this callback to inform the client.
*
* @param downloadParams params containing info about the completed download.
diff --git a/core/java/android/app/ondeviceintelligence/Feature.java b/core/java/android/app/ondeviceintelligence/Feature.java
index 4a38c92..fd0379a 100644
--- a/core/java/android/app/ondeviceintelligence/Feature.java
+++ b/core/java/android/app/ondeviceintelligence/Feature.java
@@ -34,7 +34,6 @@
@SystemApi
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
public final class Feature implements Parcelable {
- // TODO(b/325315604) - Check if we can expose non-hidden IntDefs in Framework.
private final int mId;
@Nullable
private final String mName;
diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.java b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
index f3cbd26..44930f2 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.java
+++ b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
@@ -60,6 +60,9 @@
/** Underlying service is unavailable and feature status cannot be fetched. */
public static final int FEATURE_STATUS_SERVICE_UNAVAILABLE = 4;
+ /**
+ * @hide
+ */
@IntDef(value = {
FEATURE_STATUS_UNAVAILABLE,
FEATURE_STATUS_DOWNLOADABLE,
diff --git a/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl b/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
index aba563f..8fc269e 100644
--- a/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
@@ -16,15 +16,14 @@
package android.app.ondeviceintelligence;
-import android.app.ondeviceintelligence.IProcessingSignal;
import android.os.PersistableBundle;
/**
- * Interface for Download callback to passed onto service implementation,
+ * Interface for Download callback to be passed onto service implementation,
*
* @hide
*/
-oneway interface IDownloadCallback {
+interface IDownloadCallback {
void onDownloadStarted(long bytesToDownload) = 1;
void onDownloadProgress(long bytesDownloaded) = 2;
void onDownloadFailed(int failureStatus, String errorMessage, in PersistableBundle errorParams) = 3;
diff --git a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
index 360a809..0dbe181 100644
--- a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
+++ b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
@@ -21,7 +21,7 @@
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
- import android.app.ondeviceintelligence.Content;
+ import android.os.Bundle;
import android.app.ondeviceintelligence.Feature;
import android.app.ondeviceintelligence.FeatureDetails;
import android.app.ondeviceintelligence.IDownloadCallback;
@@ -39,7 +39,7 @@
*
* @hide
*/
- oneway interface IOnDeviceIntelligenceManager {
+ interface IOnDeviceIntelligenceManager {
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
void getVersion(in RemoteCallback remoteCallback) = 1;
@@ -53,18 +53,20 @@
void getFeatureDetails(in Feature feature, in IFeatureDetailsCallback featureDetailsCallback) = 4;
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
- void requestFeatureDownload(in Feature feature, ICancellationSignal signal, in IDownloadCallback callback) = 5;
+ void requestFeatureDownload(in Feature feature, in ICancellationSignal signal, in IDownloadCallback callback) = 5;
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
- void requestTokenInfo(in Feature feature, in Content request, in ICancellationSignal signal,
+ void requestTokenInfo(in Feature feature, in Bundle requestBundle, in ICancellationSignal signal,
in ITokenInfoCallback tokenInfocallback) = 6;
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
- void processRequest(in Feature feature, in Content request, int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal signal,
- in IResponseCallback responseCallback) = 7;
+ void processRequest(in Feature feature, in Bundle requestBundle, int requestType, in ICancellationSignal cancellationSignal,
+ in IProcessingSignal signal, in IResponseCallback responseCallback) = 7;
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
void processRequestStreaming(in Feature feature,
- in Content request, int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal signal,
+ in Bundle requestBundle, int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal signal,
in IStreamingResponseCallback streamingCallback) = 8;
+
+ String getRemoteServicePackageName() = 9;
}
diff --git a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
index 0adf305..45963d2 100644
--- a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
@@ -1,8 +1,7 @@
package android.app.ondeviceintelligence;
-import android.app.ondeviceintelligence.Content;
-import android.app.ondeviceintelligence.IProcessingSignal;
import android.os.PersistableBundle;
+import android.os.Bundle;
import android.os.RemoteCallback;
/**
@@ -11,7 +10,7 @@
* @hide
*/
interface IResponseCallback {
- void onSuccess(in Content result) = 1;
+ void onSuccess(in Bundle resultBundle) = 1;
void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
- void onDataAugmentRequest(in Content content, in RemoteCallback contentCallback) = 3;
+ void onDataAugmentRequest(in Bundle processedContent, in RemoteCallback responseCallback) = 3;
}
diff --git a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
index 132e53e..671abe3 100644
--- a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
@@ -1,10 +1,8 @@
package android.app.ondeviceintelligence;
-import android.app.ondeviceintelligence.Content;
-import android.app.ondeviceintelligence.IResponseCallback;
-import android.app.ondeviceintelligence.IProcessingSignal;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
+import android.os.Bundle;
/**
@@ -13,8 +11,8 @@
* @hide
*/
interface IStreamingResponseCallback {
- void onNewContent(in Content result) = 1;
- void onSuccess(in Content result) = 2;
+ void onNewContent(in Bundle processedResult) = 1;
+ void onSuccess(in Bundle result) = 2;
void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 3;
- void onDataAugmentRequest(in Content content, in RemoteCallback contentCallback) = 4;
+ void onDataAugmentRequest(in Bundle processedContent, in RemoteCallback responseCallback) = 4;
}
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
new file mode 100644
index 0000000..03ff563a
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
@@ -0,0 +1,198 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.PersistableBundle;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * Exception type to be used for errors related to on-device intelligence system service with
+ * appropriate error code.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public class OnDeviceIntelligenceException extends Exception {
+
+ public static final int PROCESSING_ERROR_UNKNOWN = 1;
+
+ /** Request passed contains bad data for e.g. format. */
+ public static final int PROCESSING_ERROR_BAD_DATA = 2;
+
+ /** Bad request for inputs. */
+ public static final int PROCESSING_ERROR_BAD_REQUEST = 3;
+
+ /** Whole request was classified as not safe, and no response will be generated. */
+ public static final int PROCESSING_ERROR_REQUEST_NOT_SAFE = 4;
+
+ /** Underlying processing encountered an error and failed to compute results. */
+ public static final int PROCESSING_ERROR_COMPUTE_ERROR = 5;
+
+ /** Encountered an error while performing IPC */
+ public static final int PROCESSING_ERROR_IPC_ERROR = 6;
+
+ /** Request was cancelled either by user signal or by the underlying implementation. */
+ public static final int PROCESSING_ERROR_CANCELLED = 7;
+
+ /** Underlying processing in the remote implementation is not available. */
+ public static final int PROCESSING_ERROR_NOT_AVAILABLE = 8;
+
+ /** The service is currently busy. Callers should retry with exponential backoff. */
+ public static final int PROCESSING_ERROR_BUSY = 9;
+
+ /** Something went wrong with safety classification service. */
+ public static final int PROCESSING_ERROR_SAFETY_ERROR = 10;
+
+ /** Response generated was classified unsafe. */
+ public static final int PROCESSING_ERROR_RESPONSE_NOT_SAFE = 11;
+
+ /** Request is too large to be processed. */
+ public static final int PROCESSING_ERROR_REQUEST_TOO_LARGE = 12;
+
+ /** Inference suspended so that higher-priority inference can run. */
+ public static final int PROCESSING_ERROR_SUSPENDED = 13;
+
+ /**
+ * Underlying processing encountered an internal error, like a violated precondition
+ * .
+ */
+ public static final int PROCESSING_ERROR_INTERNAL = 14;
+
+ /**
+ * The processing was not able to be passed on to the remote implementation, as the
+ * service
+ * was unavailable.
+ */
+ public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15;
+ /**
+ * Error code returned when the OnDeviceIntelligenceManager service is unavailable.
+ */
+ public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 100;
+
+ /**
+ * The connection to remote service failed and the processing state could not be updated.
+ */
+ public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 200;
+
+
+ /**
+ * Error code associated with the on-device intelligence failure.
+ *
+ * @hide
+ */
+ @IntDef(
+ value = {
+ PROCESSING_ERROR_UNKNOWN,
+ PROCESSING_ERROR_BAD_DATA,
+ PROCESSING_ERROR_BAD_REQUEST,
+ PROCESSING_ERROR_REQUEST_NOT_SAFE,
+ PROCESSING_ERROR_COMPUTE_ERROR,
+ PROCESSING_ERROR_IPC_ERROR,
+ PROCESSING_ERROR_CANCELLED,
+ PROCESSING_ERROR_NOT_AVAILABLE,
+ PROCESSING_ERROR_BUSY,
+ PROCESSING_ERROR_SAFETY_ERROR,
+ PROCESSING_ERROR_RESPONSE_NOT_SAFE,
+ PROCESSING_ERROR_REQUEST_TOO_LARGE,
+ PROCESSING_ERROR_SUSPENDED,
+ PROCESSING_ERROR_INTERNAL,
+ PROCESSING_ERROR_SERVICE_UNAVAILABLE,
+ ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ PROCESSING_UPDATE_STATUS_CONNECTION_FAILED
+ }, open = true)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ @interface OnDeviceIntelligenceError {
+ }
+
+ private final int mErrorCode;
+ private final PersistableBundle mErrorParams;
+
+ /** Returns the error code of the exception. */
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ /** Returns the error params of the exception. */
+ @NonNull
+ public PersistableBundle getErrorParams() {
+ return mErrorParams;
+ }
+
+ /**
+ * Creates a new OnDeviceIntelligenceException with the specified error code, error message and
+ * error params.
+ *
+ * @param errorCode The error code.
+ * @param errorMessage The error message.
+ * @param errorParams The error params.
+ */
+ public OnDeviceIntelligenceException(
+ @OnDeviceIntelligenceError int errorCode, @NonNull String errorMessage,
+ @NonNull PersistableBundle errorParams) {
+ super(errorMessage);
+ this.mErrorCode = errorCode;
+ this.mErrorParams = errorParams;
+ }
+
+ /**
+ * Creates a new OnDeviceIntelligenceException with the specified error code and error params.
+ *
+ * @param errorCode The error code.
+ * @param errorParams The error params.
+ */
+ public OnDeviceIntelligenceException(
+ @OnDeviceIntelligenceError int errorCode,
+ @NonNull PersistableBundle errorParams) {
+ this.mErrorCode = errorCode;
+ this.mErrorParams = errorParams;
+ }
+
+ /**
+ * Creates a new OnDeviceIntelligenceException with the specified error code and error message.
+ *
+ * @param errorCode The error code.
+ * @param errorMessage The error message.
+ */
+ public OnDeviceIntelligenceException(
+ @OnDeviceIntelligenceError int errorCode, @NonNull String errorMessage) {
+ super(errorMessage);
+ this.mErrorCode = errorCode;
+ this.mErrorParams = new PersistableBundle();
+ }
+
+ /**
+ * Creates a new OnDeviceIntelligenceException with the specified error code.
+ *
+ * @param errorCode The error code.
+ */
+ public OnDeviceIntelligenceException(
+ @OnDeviceIntelligenceError int errorCode) {
+ this.mErrorCode = errorCode;
+ this.mErrorParams = new PersistableBundle();
+ }
+}
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
index d195c4d..a465e3c 100644
--- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
+++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
@@ -28,6 +28,7 @@
import android.annotation.SystemService;
import android.content.ComponentName;
import android.content.Context;
+import android.graphics.Bitmap;
import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -36,6 +37,7 @@
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.system.OsConstants;
import androidx.annotation.IntDef;
@@ -63,7 +65,7 @@
@SystemApi
@SystemService(Context.ON_DEVICE_INTELLIGENCE_SERVICE)
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public class OnDeviceIntelligenceManager {
+public final class OnDeviceIntelligenceManager {
/**
* @hide
*/
@@ -118,14 +120,13 @@
@Nullable
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
public String getRemoteServicePackageName() {
- String serviceConfigValue = mContext.getResources().getString(
- R.string.config_defaultOnDeviceSandboxedInferenceService);
- ComponentName componentName = ComponentName.unflattenFromString(serviceConfigValue);
- if (componentName != null) {
- return componentName.getPackageName();
+ String result;
+ try{
+ result = mService.getRemoteServicePackageName();
+ } catch (RemoteException e){
+ throw e.rethrowFromSystemServer();
}
-
- return null;
+ return result;
}
/**
@@ -139,7 +140,7 @@
public void getFeature(
int featureId,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull OutcomeReceiver<Feature, OnDeviceIntelligenceManagerException> featureReceiver) {
+ @NonNull OutcomeReceiver<Feature, OnDeviceIntelligenceException> featureReceiver) {
try {
IFeatureCallback callback =
new IFeatureCallback.Stub() {
@@ -154,7 +155,7 @@
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> featureReceiver.onError(
- new OnDeviceIntelligenceManagerException(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams))));
}
};
@@ -173,7 +174,7 @@
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
public void listFeatures(
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull OutcomeReceiver<List<Feature>, OnDeviceIntelligenceManagerException> featureListReceiver) {
+ @NonNull OutcomeReceiver<List<Feature>, OnDeviceIntelligenceException> featureListReceiver) {
try {
IListFeaturesCallback callback =
new IListFeaturesCallback.Stub() {
@@ -188,7 +189,7 @@
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> featureListReceiver.onError(
- new OnDeviceIntelligenceManagerException(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams))));
}
};
@@ -211,7 +212,7 @@
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
public void getFeatureDetails(@NonNull Feature feature,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull OutcomeReceiver<FeatureDetails, OnDeviceIntelligenceManagerException> featureDetailsReceiver) {
+ @NonNull OutcomeReceiver<FeatureDetails, OnDeviceIntelligenceException> featureDetailsReceiver) {
try {
IFeatureDetailsCallback callback = new IFeatureDetailsCallback.Stub() {
@@ -226,7 +227,7 @@
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> featureDetailsReceiver.onError(
- new OnDeviceIntelligenceManagerException(errorCode,
+ new OnDeviceIntelligenceException(errorCode,
errorMessage, errorParams))));
}
};
@@ -243,9 +244,8 @@
*
* Note: If a feature was already requested for downloaded previously, the onDownloadFailed
* callback would be invoked with {@link DownloadCallback#DOWNLOAD_FAILURE_STATUS_DOWNLOADING}.
- * In such cases, clients should query the feature status via {@link #getFeatureStatus} to
- * check
- * on the feature's download status.
+ * In such cases, clients should query the feature status via {@link #getFeatureDetails} to
+ * check on the feature's download status.
*
* @param feature feature to request download for.
* @param callback callback to populate updates about download status.
@@ -284,7 +284,7 @@
@Override
public void onDownloadCompleted(PersistableBundle downloadParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
- () -> onDownloadCompleted(downloadParams)));
+ () -> callback.onDownloadCompleted(downloadParams)));
}
};
@@ -305,7 +305,8 @@
* provided {@link Feature}.
*
* @param feature feature associated with the request.
- * @param request request that contains the content data and associated params.
+ * @param request request and associated params represented by the Bundle
+ * data.
* @param outcomeReceiver callback to populate the token info or exception in case of
* failure.
* @param cancellationSignal signal to invoke cancellation on the operation in the remote
@@ -313,11 +314,11 @@
* @param callbackExecutor executor to run the callback on.
*/
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
- public void requestTokenInfo(@NonNull Feature feature, @NonNull Content request,
+ public void requestTokenInfo(@NonNull Feature feature, @NonNull @InferenceParams Bundle request,
@Nullable CancellationSignal cancellationSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull OutcomeReceiver<TokenInfo,
- OnDeviceIntelligenceManagerException> outcomeReceiver) {
+ OnDeviceIntelligenceException> outcomeReceiver) {
try {
ITokenInfoCallback callback = new ITokenInfoCallback.Stub() {
@Override
@@ -331,7 +332,7 @@
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> outcomeReceiver.onError(
- new OnDeviceIntelligenceManagerProcessingException(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams))));
}
};
@@ -357,30 +358,30 @@
* failure.
*
* @param feature feature associated with the request.
- * @param request request that contains the Content data and
- * associated params.
+ * @param request request and associated params represented by the Bundle
+ * data.
* @param requestType type of request being sent for processing the content.
* @param cancellationSignal signal to invoke cancellation.
* @param processingSignal signal to send custom signals in the
* remote implementation.
* @param callbackExecutor executor to run the callback on.
- * @param responseCallback callback to populate the response content and
+ * @param processingCallback callback to populate the response content and
* associated params.
*/
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
- public void processRequest(@NonNull Feature feature, @Nullable Content request,
+ public void processRequest(@NonNull Feature feature, @NonNull @InferenceParams Bundle request,
@RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull ProcessingOutcomeReceiver responseCallback) {
+ @NonNull ProcessingCallback processingCallback) {
try {
IResponseCallback callback = new IResponseCallback.Stub() {
@Override
- public void onSuccess(Content result) {
+ public void onSuccess(@InferenceParams Bundle result) {
Binder.withCleanCallingIdentity(() -> {
- callbackExecutor.execute(() -> responseCallback.onResult(result));
+ callbackExecutor.execute(() -> processingCallback.onResult(result));
});
}
@@ -388,16 +389,16 @@
public void onFailure(int errorCode, String errorMessage,
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
- () -> responseCallback.onError(
- new OnDeviceIntelligenceManagerProcessingException(
+ () -> processingCallback.onError(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams))));
}
@Override
- public void onDataAugmentRequest(@NonNull Content content,
+ public void onDataAugmentRequest(@NonNull @InferenceParams Bundle request,
@NonNull RemoteCallback contentCallback) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
- () -> responseCallback.onDataAugmentRequest(content, result -> {
+ () -> processingCallback.onDataAugmentRequest(request, result -> {
Bundle bundle = new Bundle();
bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY, result);
callbackExecutor.execute(() -> contentCallback.sendResult(bundle));
@@ -430,15 +431,15 @@
* Variation of {@link #processRequest} that asynchronously processes a request in a
* streaming
* fashion, where new content is pushed to caller in chunks via the
- * {@link StreamedProcessingOutcomeReceiver#onNewContent}. After the streaming is complete,
- * the service should call {@link StreamedProcessingOutcomeReceiver#onResult} and can optionally
- * populate the complete the full response {@link Content} as part of the callback in cases
- * when the final response contains an enhanced aggregation of the Contents already
+ * {@link StreamingProcessingCallback#onPartialResult}. After the streaming is complete,
+ * the service should call {@link StreamingProcessingCallback#onResult} and can optionally
+ * populate the complete the full response {@link Bundle} as part of the callback in cases
+ * when the final response contains an enhanced aggregation of the contents already
* streamed.
*
* @param feature feature associated with the request.
- * @param request request that contains the Content data and associated
- * params.
+ * @param request request and associated params represented by the Bundle
+ * data.
* @param requestType type of request being sent for processing the content.
* @param cancellationSignal signal to invoke cancellation.
* @param processingSignal signal to send custom signals in the
@@ -448,27 +449,27 @@
* @param callbackExecutor executor to run the callback on.
*/
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
- public void processRequestStreaming(@NonNull Feature feature, @Nullable Content request,
+ public void processRequestStreaming(@NonNull Feature feature, @NonNull @InferenceParams Bundle request,
@RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull StreamedProcessingOutcomeReceiver streamingResponseCallback) {
+ @NonNull StreamingProcessingCallback streamingProcessingCallback) {
try {
IStreamingResponseCallback callback = new IStreamingResponseCallback.Stub() {
@Override
- public void onNewContent(Content result) {
+ public void onNewContent(@InferenceParams Bundle result) {
Binder.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
- () -> streamingResponseCallback.onNewContent(result));
+ () -> streamingProcessingCallback.onPartialResult(result));
});
}
@Override
- public void onSuccess(Content result) {
+ public void onSuccess(@InferenceParams Bundle result) {
Binder.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
- () -> streamingResponseCallback.onResult(result));
+ () -> streamingProcessingCallback.onResult(result));
});
}
@@ -477,18 +478,18 @@
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
- () -> streamingResponseCallback.onError(
- new OnDeviceIntelligenceManagerProcessingException(
+ () -> streamingProcessingCallback.onError(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams)));
});
}
@Override
- public void onDataAugmentRequest(@NonNull Content content,
+ public void onDataAugmentRequest(@NonNull @InferenceParams Bundle content,
@NonNull RemoteCallback contentCallback) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
- () -> streamingResponseCallback.onDataAugmentRequest(content,
+ () -> streamingProcessingCallback.onDataAugmentRequest(content,
contentResponse -> {
Bundle bundle = new Bundle();
bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY,
@@ -519,7 +520,7 @@
}
- /** Request inference with provided Content and Params. */
+ /** Request inference with provided Bundle and Params. */
public static final int REQUEST_TYPE_INFERENCE = 0;
/**
@@ -530,7 +531,7 @@
*/
public static final int REQUEST_TYPE_PREPARE = 1;
- /** Request Embeddings of the passed-in Content. */
+ /** Request Embeddings of the passed-in Bundle. */
public static final int REQUEST_TYPE_EMBEDDINGS = 2;
/**
@@ -547,154 +548,30 @@
public @interface RequestType {
}
-
/**
- * Exception type to be populated in callbacks to the methods under
- * {@link OnDeviceIntelligenceManager}.
+ * {@link Bundle}s annotated with this type will be validated that they are in-effect read-only
+ * when passed to inference service via Binder IPC. Following restrictions apply :
+ * <ul>
+ * <li> Any primitive types or their collections can be added as usual.</li>
+ * <li>IBinder objects should *not* be added.</li>
+ * <li>Parcelable data which has no active-objects, should be added as
+ * {@link Bundle#putByteArray}</li>
+ * <li>Parcelables have active-objects, only following types will be allowed</li>
+ * <ul>
+ * <li>{@link Bitmap} set as {@link Bitmap#setImmutable()}</li>
+ * <li>{@link android.database.CursorWindow}</li>
+ * <li>{@link android.os.ParcelFileDescriptor} opened in
+ * {@link android.os.ParcelFileDescriptor#MODE_READ_ONLY}</li>
+ * <li>{@link android.os.SharedMemory} set to {@link OsConstants#PROT_READ}</li>
+ * </ul>
+ * </ul>
+ *
+ * In all other scenarios the system-server might throw a
+ * {@link android.os.BadParcelableException} if the Bundle validation fails.
+ *
+ * @hide
*/
- public static class OnDeviceIntelligenceManagerException extends Exception {
- /**
- * Error code returned when the OnDeviceIntelligenceManager service is unavailable.
- */
- public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 1000;
-
- /**
- * Error code to be used for on device intelligence manager failures.
- *
- * @hide
- */
- @IntDef(
- value = {
- ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE
- }, open = true)
- @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
- @interface OnDeviceIntelligenceManagerErrorCode {
- }
-
- private final int mErrorCode;
- private final PersistableBundle errorParams;
-
- public OnDeviceIntelligenceManagerException(
- @OnDeviceIntelligenceManagerErrorCode int errorCode, @NonNull String errorMessage,
- @NonNull PersistableBundle errorParams) {
- super(errorMessage);
- this.mErrorCode = errorCode;
- this.errorParams = errorParams;
- }
-
- public OnDeviceIntelligenceManagerException(
- @OnDeviceIntelligenceManagerErrorCode int errorCode,
- @NonNull PersistableBundle errorParams) {
- this.mErrorCode = errorCode;
- this.errorParams = errorParams;
- }
-
- public int getErrorCode() {
- return mErrorCode;
- }
-
- @NonNull
- public PersistableBundle getErrorParams() {
- return errorParams;
- }
- }
-
- /**
- * Exception type to be populated in callbacks to the methods under
- * {@link OnDeviceIntelligenceManager#processRequest} or
- * {@link OnDeviceIntelligenceManager#processRequestStreaming} .
- */
- public static class OnDeviceIntelligenceManagerProcessingException extends
- OnDeviceIntelligenceManagerException {
-
- public static final int PROCESSING_ERROR_UNKNOWN = 1;
-
- /** Request passed contains bad data for e.g. format. */
- public static final int PROCESSING_ERROR_BAD_DATA = 2;
-
- /** Bad request for inputs. */
- public static final int PROCESSING_ERROR_BAD_REQUEST = 3;
-
- /** Whole request was classified as not safe, and no response will be generated. */
- public static final int PROCESSING_ERROR_REQUEST_NOT_SAFE = 4;
-
- /** Underlying processing encountered an error and failed to compute results. */
- public static final int PROCESSING_ERROR_COMPUTE_ERROR = 5;
-
- /** Encountered an error while performing IPC */
- public static final int PROCESSING_ERROR_IPC_ERROR = 6;
-
- /** Request was cancelled either by user signal or by the underlying implementation. */
- public static final int PROCESSING_ERROR_CANCELLED = 7;
-
- /** Underlying processing in the remote implementation is not available. */
- public static final int PROCESSING_ERROR_NOT_AVAILABLE = 8;
-
- /** The service is currently busy. Callers should retry with exponential backoff. */
- public static final int PROCESSING_ERROR_BUSY = 9;
-
- /** Something went wrong with safety classification service. */
- public static final int PROCESSING_ERROR_SAFETY_ERROR = 10;
-
- /** Response generated was classified unsafe. */
- public static final int PROCESSING_ERROR_RESPONSE_NOT_SAFE = 11;
-
- /** Request is too large to be processed. */
- public static final int PROCESSING_ERROR_REQUEST_TOO_LARGE = 12;
-
- /** Inference suspended so that higher-priority inference can run. */
- public static final int PROCESSING_ERROR_SUSPENDED = 13;
-
- /**
- * Underlying processing encountered an internal error, like a violated precondition
- * .
- */
- public static final int PROCESSING_ERROR_INTERNAL = 14;
-
- /**
- * The processing was not able to be passed on to the remote implementation, as the
- * service
- * was unavailable.
- */
- public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15;
-
- /**
- * Error code of failed processing request.
- *
- * @hide
- */
- @IntDef(
- value = {
- PROCESSING_ERROR_UNKNOWN,
- PROCESSING_ERROR_BAD_DATA,
- PROCESSING_ERROR_BAD_REQUEST,
- PROCESSING_ERROR_REQUEST_NOT_SAFE,
- PROCESSING_ERROR_COMPUTE_ERROR,
- PROCESSING_ERROR_IPC_ERROR,
- PROCESSING_ERROR_CANCELLED,
- PROCESSING_ERROR_NOT_AVAILABLE,
- PROCESSING_ERROR_BUSY,
- PROCESSING_ERROR_SAFETY_ERROR,
- PROCESSING_ERROR_RESPONSE_NOT_SAFE,
- PROCESSING_ERROR_REQUEST_TOO_LARGE,
- PROCESSING_ERROR_SUSPENDED,
- PROCESSING_ERROR_INTERNAL,
- PROCESSING_ERROR_SERVICE_UNAVAILABLE
- }, open = true)
- @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
- @interface ProcessingError {
- }
-
- public OnDeviceIntelligenceManagerProcessingException(
- @ProcessingError int errorCode, @NonNull String errorMessage,
- @NonNull PersistableBundle errorParams) {
- super(errorCode, errorMessage, errorParams);
- }
-
- public OnDeviceIntelligenceManagerProcessingException(
- @ProcessingError int errorCode,
- @NonNull PersistableBundle errorParams) {
- super(errorCode, errorParams);
- }
+ @Target({ElementType.PARAMETER, ElementType.FIELD})
+ public @interface InferenceParams {
}
}
diff --git a/core/java/android/app/ondeviceintelligence/ProcessingCallback.java b/core/java/android/app/ondeviceintelligence/ProcessingCallback.java
new file mode 100644
index 0000000..4d936ea
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/ProcessingCallback.java
@@ -0,0 +1,71 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams;
+
+import java.util.function.Consumer;
+
+/**
+ * Callback to populate the processed response or any error that occurred during the
+ * request processing. This callback also provides a method to request additional data to be
+ * augmented to the request-processing, using the partial response that was already
+ * processed in the remote implementation.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public interface ProcessingCallback {
+ /**
+ * Invoked when request has been processed and result is ready to be propagated to the
+ * caller.
+ *
+ * @param result Response to be passed as a result.
+ */
+ void onResult(@NonNull @InferenceParams Bundle result);
+
+ /**
+ * Called when the request processing fails. The failure details are indicated by the
+ * {@link OnDeviceIntelligenceException} passed as an argument to this method.
+ *
+ * @param error An exception with more details about the error that occurred.
+ */
+ void onError(@NonNull OnDeviceIntelligenceException error);
+
+ /**
+ * Callback to be invoked in cases where the remote service needs to perform retrieval or
+ * transformation operations based on a partially processed request, in order to augment the
+ * final response, by using the additional context sent via this callback.
+ *
+ * @param processedContent The content payload that should be used to augment ongoing request.
+ * @param contentConsumer The augmentation data that should be sent to remote
+ * service for further processing a request. Bundle passed in here is
+ * expected to be non-null or EMPTY when there is no response.
+ */
+ default void onDataAugmentRequest(
+ @NonNull @InferenceParams Bundle processedContent,
+ @NonNull Consumer<Bundle> contentConsumer) {
+ contentConsumer.accept(Bundle.EMPTY);
+ }
+}
diff --git a/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java b/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java
deleted file mode 100644
index b0b6e19..0000000
--- a/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java
+++ /dev/null
@@ -1,54 +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 android.app.ondeviceintelligence;
-
-import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
-
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-import android.annotation.SystemApi;
-import android.os.OutcomeReceiver;
-
-import java.util.function.Consumer;
-
-/**
- * Response Callback to populate the processed response or any error that occurred during the
- * request processing. This callback also provides a method to request additional data to be
- * augmented to the request-processing, using the partial {@link Content} that was already
- * processed in the remote implementation.
- *
- * @hide
- */
-@SystemApi
-@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public interface ProcessingOutcomeReceiver extends
- OutcomeReceiver<Content,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> {
- /**
- * Callback to be invoked in cases where the remote service needs to perform retrieval or
- * transformation operations based on a partially processed request, in order to augment the
- * final response, by using the additional context sent via this callback.
- *
- * @param content The content payload that should be used to augment ongoing request.
- * @param contentConsumer The augmentation data that should be sent to remote
- * service for further processing a request.
- */
- default void onDataAugmentRequest(@NonNull Content content,
- @NonNull Consumer<Content> contentConsumer) {
- contentConsumer.accept(null);
- }
-}
diff --git a/core/java/android/app/ondeviceintelligence/ProcessingSignal.java b/core/java/android/app/ondeviceintelligence/ProcessingSignal.java
index 3e543d2..c275cc7 100644
--- a/core/java/android/app/ondeviceintelligence/ProcessingSignal.java
+++ b/core/java/android/app/ondeviceintelligence/ProcessingSignal.java
@@ -75,7 +75,11 @@
/**
- * Sends a custom signal with the provided parameters. It also signals the remote callback
+ * Sends a custom signal with the provided parameters. If there are multiple concurrent
+ * requests to this method, the actionParams are queued in a blocking fashion, in the order they
+ * are received.
+ *
+ * It also signals the remote callback
* with the same params if already configured, if not the action is queued to be sent when a
* remote is configured. Similarly, on the receiver side, the callback will be invoked if
* already set, if not all actions are queued to be sent to callback when it is set.
@@ -159,9 +163,9 @@
* Sets the remote transport.
*
* If there are actions queued from {@link ProcessingSignal#sendSignal}, they are also
- * sequentially sent to the remote.
+ * sequentially sent to the configured remote.
*
- * This method is guaranteed that the remote transport will not be called after it
+ * This method guarantees that the remote transport will not be called after it
* has been removed.
*
* @param remote The remote transport, or null to remove.
diff --git a/core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java b/core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
similarity index 65%
rename from core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
rename to core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
index ac2b032..41f1807 100644
--- a/core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
+++ b/core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
@@ -21,19 +21,21 @@
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams;
/**
- * Streaming variant of outcome receiver to populate response while processing a given request,
- * possibly in chunks to provide a async processing behaviour to the caller.
+ * Streaming variant of {@link ProcessingCallback} to populate response while processing a given
+ * request, possibly in chunks to provide a async processing behaviour to the caller.
*
* @hide
*/
@SystemApi
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public interface StreamedProcessingOutcomeReceiver extends ProcessingOutcomeReceiver {
+public interface StreamingProcessingCallback extends ProcessingCallback {
/**
- * Callback that would be invoked when a part of the response i.e. some {@link Content} is
- * already processed and needs to be passed onto the caller.
+ * Callback that would be invoked when a part of the response i.e. some response is
+ * already processed, and needs to be passed onto the caller.
*/
- void onNewContent(@NonNull Content content);
+ void onPartialResult(@NonNull @InferenceParams Bundle partialResult);
}
diff --git a/core/java/android/app/servertransaction/WindowStateResizeItem.java b/core/java/android/app/servertransaction/WindowStateResizeItem.java
index 193b03c..fedffe1 100644
--- a/core/java/android/app/servertransaction/WindowStateResizeItem.java
+++ b/core/java/android/app/servertransaction/WindowStateResizeItem.java
@@ -28,6 +28,7 @@
import android.os.Parcel;
import android.os.RemoteException;
import android.os.Trace;
+import android.util.Log;
import android.util.MergedConfiguration;
import android.view.IWindow;
import android.view.InsetsState;
@@ -41,6 +42,8 @@
*/
public class WindowStateResizeItem extends ClientTransactionItem {
+ private static final String TAG = "WindowStateResizeItem";
+
private IWindow mWindow;
private ClientWindowFrames mFrames;
private boolean mReportDraw;
@@ -65,7 +68,9 @@
mAlwaysConsumeSystemBars, mDisplayId, mSyncSeqId, mDragResizing);
} catch (RemoteException e) {
// Should be a local call.
- throw new RuntimeException(e);
+ // An exception could happen if the process is restarted. It is safe to ignore since
+ // the window should no longer exist.
+ Log.w(TAG, "The original window no longer exists in the new process", e);
}
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
index 9573e6f..df6d2a6 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -246,7 +246,10 @@
* @param executor Executor on which to run the consumer callback
* @param statusConsumer A consumer that handles the status codes, which is returned
* right after the call.
+ * @deprecated Use {@link #provideConnection(ParcelFileDescriptor, Executor, Consumer)} instead
+ * to provide a remote wearable device connection to the WearableSensingService
*/
+ @Deprecated
@RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
public void provideDataStream(
@NonNull ParcelFileDescriptor parcelFileDescriptor,
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 7f2ec53..1653bf5 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5396,6 +5396,19 @@
public static final String SMARTSPACE_SERVICE = "smartspace";
/**
+ * Used for getting the contextual search service.
+ *
+ * <p><b>NOTE: </b> this service is optional; callers of
+ * {@code Context.getSystemServiceName(CONTEXTUAL_SEARCH_SERVICE)} must check for {@code null}.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ @FlaggedApi(android.app.contextualsearch.flags.Flags.FLAG_ENABLE_SERVICE)
+ @SystemApi
+ public static final String CONTEXTUAL_SEARCH_SERVICE = "contextual_search";
+
+ /**
* Used for getting the cloudsearch service.
*
* <p><b>NOTE: </b> this service is optional; callers of
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 443aadd..8bc5e8c 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -6129,10 +6129,14 @@
* the selection changes made by the user.
* Applications may implement this method to change any of the following Chooser arguments by
* returning new values in the result bundle:
- * {@link #EXTRA_CHOOSER_TARGETS}, {@link #EXTRA_ALTERNATE_INTENTS},
+ * {@link #EXTRA_CHOOSER_TARGETS},
+ * {@link #EXTRA_ALTERNATE_INTENTS},
* {@link #EXTRA_CHOOSER_CUSTOM_ACTIONS},
* {@link #EXTRA_CHOOSER_MODIFY_SHARE_ACTION},
- * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER}.</p>
+ * {@link #EXTRA_METADATA_TEXT},
+ * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER},
+ * {@link #EXTRA_CHOOSER_RESULT_INTENT_SENDER}.
+ * </p>
*/
@FlaggedApi(android.service.chooser.Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING)
public static final String EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI =
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 3a5383d..39b9149 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -693,7 +693,12 @@
*
* <p>If the caller is running on a managed profile, it'll return only the current profile.
* Otherwise it'll return the same list as {@link UserManager#getUserProfiles()} would.
+ *
+ * <p> To get hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have
+ * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required.
*/
+ @RequiresPermission(conditional = true,
+ anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
public List<UserHandle> getProfiles() {
if (mUserManager.isManagedProfile()
|| (android.multiuser.Flags.enableLauncherAppsHiddenProfileChecks()
@@ -751,11 +756,17 @@
* list.</li>
* </ul>
*
+ * <p>If the user in question is a hidden profile
+ * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have
+ * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required.
+ *
* @param packageName The specific package to query. If null, it checks all installed packages
* in the profile.
* @param user The UserHandle of the profile.
* @return List of launchable activities. Can be an empty list but will not be null.
*/
+ @RequiresPermission(conditional = true,
+ anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
logErrorForInvalidProfileAccess(user);
try {
@@ -925,10 +936,16 @@
* Returns the activity info for a given intent and user handle, if it resolves. Otherwise it
* returns null.
*
+ * <p>If the user in question is a hidden profile
+ * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have
+ * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required.
+ *
* @param intent The intent to find a match for.
* @param user The profile to look in for a match.
* @return An activity info object if there is a match.
*/
+ @RequiresPermission(conditional = true,
+ anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
public LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) {
logErrorForInvalidProfileAccess(user);
try {
@@ -978,11 +995,17 @@
/**
* Starts a Main activity in the specified profile.
*
+ * <p>If the user in question is a hidden profile
+ * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have
+ * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required.
+ *
* @param component The ComponentName of the activity to launch
* @param user The UserHandle of the profile
* @param sourceBounds The Rect containing the source bounds of the clicked icon
* @param opts Options to pass to startActivity
*/
+ @RequiresPermission(conditional = true,
+ anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
public void startMainActivity(ComponentName component, UserHandle user, Rect sourceBounds,
Bundle opts) {
logErrorForInvalidProfileAccess(user);
@@ -1020,11 +1043,17 @@
* Starts the settings activity to show the application details for a
* package in the specified profile.
*
+ * <p>If the user in question is a hidden profile
+ * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have
+ * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required.
+ *
* @param component The ComponentName of the package to launch settings for.
* @param user The UserHandle of the profile
* @param sourceBounds The Rect containing the source bounds of the clicked icon
* @param opts Options to pass to startActivity
*/
+ @RequiresPermission(conditional = true,
+ anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
public void startAppDetailsActivity(ComponentName component, UserHandle user,
Rect sourceBounds, Bundle opts) {
logErrorForInvalidProfileAccess(user);
@@ -1135,11 +1164,17 @@
/**
* Checks if the package is installed and enabled for a profile.
*
+ * <p>If the user in question is a hidden profile
+ * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have
+ * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required.
+ *
* @param packageName The package to check.
* @param user The UserHandle of the profile.
*
* @return true if the package exists and is enabled.
*/
+ @RequiresPermission(conditional = true,
+ anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
public boolean isPackageEnabled(String packageName, UserHandle user) {
logErrorForInvalidProfileAccess(user);
try {
@@ -1157,6 +1192,10 @@
* <p>The contents of this {@link Bundle} are supposed to be a contract between the suspending
* app and the launcher.
*
+ * <p>If the user in question is a hidden profile
+ * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have
+ * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required.
+ *
* <p>Note: This just returns whatever extras were provided to the system, <em>which might
* even be {@code null}.</em>
*
@@ -1168,6 +1207,8 @@
* @see Callback#onPackagesSuspended(String[], UserHandle, Bundle)
* @see PackageManager#isPackageSuspended()
*/
+ @RequiresPermission(conditional = true,
+ anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
public @Nullable Bundle getSuspendedPackageLauncherExtras(String packageName, UserHandle user) {
logErrorForInvalidProfileAccess(user);
try {
@@ -1182,10 +1223,16 @@
* could be done because the package was marked as distracting to the user via
* {@code PackageManager.setDistractingPackageRestrictions(String[], int)}.
*
+ * <p>If the user in question is a hidden profile
+ * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have
+ * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required.
+ *
* @param packageName The package for which to check.
* @param user the {@link UserHandle} of the profile.
* @return
*/
+ @RequiresPermission(conditional = true,
+ anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
public boolean shouldHideFromSuggestions(@NonNull String packageName,
@NonNull UserHandle user) {
Objects.requireNonNull(packageName, "packageName");
@@ -1200,6 +1247,10 @@
/**
* Returns {@link ApplicationInfo} about an application installed for a specific user profile.
*
+ * <p>If the user in question is a hidden profile
+ * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have
+ * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required.
+ *
* @param packageName The package name of the application
* @param flags Additional option flags {@link PackageManager#getApplicationInfo}
* @param user The UserHandle of the profile.
@@ -1208,6 +1259,8 @@
* {@code null} if the package isn't installed for the given profile, or the profile
* isn't enabled.
*/
+ @RequiresPermission(conditional = true,
+ anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
public ApplicationInfo getApplicationInfo(@NonNull String packageName,
@ApplicationInfoFlagsBits int flags, @NonNull UserHandle user)
throws PackageManager.NameNotFoundException {
@@ -1257,11 +1310,17 @@
* <p>The activity may still not be exported, in which case {@link #startMainActivity} will
* throw a {@link SecurityException} unless the caller has the same UID as the target app's.
*
+ * <p>If the user in question is a hidden profile
+ * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have
+ * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required.
+ *
* @param component The activity to check.
* @param user The UserHandle of the profile.
*
* @return true if the activity exists and is enabled.
*/
+ @RequiresPermission(conditional = true,
+ anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
public boolean isActivityEnabled(ComponentName component, UserHandle user) {
logErrorForInvalidProfileAccess(user);
try {
@@ -1816,8 +1875,14 @@
/**
* Registers a callback for changes to packages in this user and managed profiles.
*
+ * <p>To receive callbacks for hidden profile{@link UserManager.USER_TYPE_PROFILE_PRIVATE},
+ * caller should have {@link android.app.role.RoleManager.ROLE_HOME} and either of the
+ * permissions required.
+ *
* @param callback The callback to register.
*/
+ @RequiresPermission(conditional = true,
+ anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
public void registerCallback(Callback callback) {
registerCallback(callback, null);
}
@@ -1825,9 +1890,15 @@
/**
* Registers a callback for changes to packages in this user and managed profiles.
*
+ * <p>To receive callbacks for hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE},
+ * caller should have {@link android.app.role.RoleManager.ROLE_HOME} and either of the
+ * permissions required.
+ *
* @param callback The callback to register.
* @param handler that should be used to post callbacks on, may be null.
*/
+ @RequiresPermission(conditional = true,
+ anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
public void registerCallback(Callback callback, Handler handler) {
synchronized (this) {
if (callback != null && findCallbackLocked(callback) < 0) {
@@ -2275,8 +2346,14 @@
* package name in the app's manifest, have the android.permission.QUERY_ALL_PACKAGES, or be
* the session owner to retrieve these details.
*
+ * <p>If the user in question is a hidden profile
+ * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have
+ * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required.
+ *
* @see PackageInstaller#getAllSessions()
*/
+ @RequiresPermission(conditional = true,
+ anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
public @NonNull List<SessionInfo> getAllPackageInstallerSessions() {
try {
return mService.getAllSessions(mContext.getPackageName()).getList();
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 48a7cc9..2d32aed 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -108,6 +108,7 @@
is_fixed_read_only: true
}
+# This flag guards the private space feature and all its implementations excluding the APIs. APIs are guarded by android.os.Flags.allow_private_profile.
flag {
name: "enable_private_space_features"
namespace: "profile_experiences"
@@ -192,3 +193,10 @@
bug: "290333800"
is_fixed_read_only: true
}
+
+flag {
+ name: "delete_private_space_from_reset"
+ namespace: "profile_experiences"
+ description: "Add entrypoint in Settings Reset options for deleting private space when lock is forgotten"
+ bug: "329601751"
+}
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index 09e59d3..47edba6 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -72,14 +72,20 @@
flag {
namespace: "credential_manager"
- name: "clear_credentials_api_fix_enabled"
+ name: "clear_credentials_fix_enabled"
description: "Fixes bug in clearCredential API that causes indefinite suspension"
bug: "314926460"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
namespace: "credential_manager"
- name: "hybrid_filter_fix_enabled"
+ name: "hybrid_filter_opt_fix_enabled"
description: "Removes capability check from hybrid implementation"
bug: "323923403"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index dfb77c0..faa2c70 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -34,22 +34,17 @@
import android.util.LruCache;
import android.util.Pair;
import android.util.Printer;
-import com.android.internal.util.RingBuffer;
import dalvik.system.BlockGuard;
import dalvik.system.CloseGuard;
-
import java.io.File;
import java.io.IOException;
import java.lang.ref.Reference;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
-import java.util.Locale;
import java.util.Map;
import java.util.function.BinaryOperator;
import java.util.function.UnaryOperator;
@@ -190,7 +185,7 @@
SQLiteDatabaseConfiguration configuration,
int connectionId, boolean primaryConnection) {
mPool = pool;
- mRecentOperations = new OperationLog();
+ mRecentOperations = new OperationLog(mPool);
mConfiguration = new SQLiteDatabaseConfiguration(configuration);
mConnectionId = connectionId;
mIsPrimaryConnection = primaryConnection;
@@ -312,16 +307,6 @@
}
}
- /** Record the start of a transaction for logging and debugging. */
- void recordBeginTransaction(String mode) {
- mRecentOperations.beginTransaction(mode);
- }
-
- /** Record the end of a transaction for logging and debugging. */
- void recordEndTransaction(boolean successful) {
- mRecentOperations.endTransaction(successful);
- }
-
private void setPageSize() {
if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
final long newValue = SQLiteGlobal.getDefaultPageSize();
@@ -1352,7 +1337,6 @@
}
printer.println(" isPrimaryConnection: " + mIsPrimaryConnection);
printer.println(" onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations);
- printer.println(" totalLongOperations: " + mRecentOperations.getTotalLongOperations());
mRecentOperations.dump(printer);
@@ -1611,39 +1595,51 @@
}
}
- private final class OperationLog {
+ private static final class OperationLog {
private static final int MAX_RECENT_OPERATIONS = 20;
private static final int COOKIE_GENERATION_SHIFT = 8;
private static final int COOKIE_INDEX_MASK = 0xff;
- // Operations over 2s are long. Save the last ten.
- private static final long LONG_OPERATION_THRESHOLD_MS = 2_000;
- private static final int MAX_LONG_OPERATIONS = 10;
-
private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS];
- private int mIndex = -1;
- private int mGeneration = 0;
- private final Operation mTransaction = new Operation();
+ private int mIndex;
+ private int mGeneration;
+ private final SQLiteConnectionPool mPool;
private long mResultLong = Long.MIN_VALUE;
private String mResultString;
- private final RingBuffer<Operation> mLongOperations =
- new RingBuffer<>(()->{return new Operation();},
- (n) ->{return new Operation[n];},
- MAX_LONG_OPERATIONS);
- private int mTotalLongOperations = 0;
+ OperationLog(SQLiteConnectionPool pool) {
+ mPool = pool;
+ }
public int beginOperation(String kind, String sql, Object[] bindArgs) {
mResultLong = Long.MIN_VALUE;
mResultString = null;
synchronized (mOperations) {
- Operation operation = newOperationLocked();
+ final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS;
+ Operation operation = mOperations[index];
+ if (operation == null) {
+ operation = new Operation();
+ mOperations[index] = operation;
+ } else {
+ operation.mFinished = false;
+ operation.mException = null;
+ if (operation.mBindArgs != null) {
+ operation.mBindArgs.clear();
+ }
+ }
+ operation.mStartWallTime = System.currentTimeMillis();
+ operation.mStartTime = SystemClock.uptimeMillis();
operation.mKind = kind;
operation.mSql = sql;
+ operation.mPath = mPool.getPath();
+ operation.mResultLong = Long.MIN_VALUE;
+ operation.mResultString = null;
if (bindArgs != null) {
if (operation.mBindArgs == null) {
operation.mBindArgs = new ArrayList<Object>();
+ } else {
+ operation.mBindArgs.clear();
}
for (int i = 0; i < bindArgs.length; i++) {
final Object arg = bindArgs[i];
@@ -1655,44 +1651,16 @@
}
}
}
+ operation.mCookie = newOperationCookieLocked(index);
if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
operation.mCookie);
}
+ mIndex = index;
return operation.mCookie;
}
}
- public void beginTransaction(String kind) {
- synchronized (mOperations) {
- Operation operation = newOperationLocked();
- operation.mKind = kind;
- mTransaction.copyFrom(operation);
-
- if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) {
- Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
- operation.mCookie);
- }
- }
- }
-
- /**
- * Fetch a new operation from the ring buffer. The operation is properly initialized.
- * This advances mIndex to point to the next element.
- */
- private Operation newOperationLocked() {
- final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS;
- Operation operation = mOperations[index];
- if (operation == null) {
- mOperations[index] = new Operation();
- operation = mOperations[index];
- }
- operation.start();
- operation.mCookie = newOperationCookieLocked(index);
- mIndex = index;
- return operation;
- }
-
public void failOperation(int cookie, Exception ex) {
synchronized (mOperations) {
final Operation operation = getOperationLocked(cookie);
@@ -1716,20 +1684,6 @@
}
}
- public boolean endTransaction(boolean success) {
- synchronized (mOperations) {
- mTransaction.mResultLong = success ? 1 : 0;
- final long execTime = finishOperationLocked(mTransaction);
- final Operation operation = getOperationLocked(mTransaction.mCookie);
- if (operation != null) {
- operation.copyFrom(mTransaction);
- }
- mTransaction.setEmpty();
- return NoPreloadHolder.DEBUG_LOG_SLOW_QUERIES
- && SQLiteDebug.shouldLogSlowQuery(execTime);
- }
- }
-
public void logOperation(int cookie, String detail) {
synchronized (mOperations) {
logOperationLocked(cookie, detail);
@@ -1751,7 +1705,9 @@
Trace.asyncTraceEnd(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
operation.mCookie);
}
- final long execTime = finishOperationLocked(operation);
+ operation.mEndTime = SystemClock.uptimeMillis();
+ operation.mFinished = true;
+ final long execTime = operation.mEndTime - operation.mStartTime;
mPool.onStatementExecuted(execTime);
return NoPreloadHolder.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery(
execTime);
@@ -1776,22 +1732,10 @@
return generation << COOKIE_GENERATION_SHIFT | index;
}
- /** Close out the operation and return the elapsed time. */
- private long finishOperationLocked(Operation operation) {
- operation.mEndTime = SystemClock.uptimeMillis();
- operation.mFinished = true;
- final long elapsed = operation.mEndTime - operation.mStartTime;
- if (elapsed > LONG_OPERATION_THRESHOLD_MS) {
- mLongOperations.getNextSlot().copyFrom(operation);
- mTotalLongOperations++;
- }
- return elapsed;
- }
-
private Operation getOperationLocked(int cookie) {
final int index = cookie & COOKIE_INDEX_MASK;
final Operation operation = mOperations[index];
- return (operation != null && operation.mCookie == cookie) ? operation : null;
+ return operation.mCookie == cookie ? operation : null;
}
public String describeCurrentOperation() {
@@ -1806,87 +1750,48 @@
}
}
- /**
- * Dump an Operation if it is not in the recent operations list. Return 1 if the
- * operation was dumped and 0 if not.
- */
- private int dumpIfNotRecentLocked(Printer pw, Operation op, int counter) {
- if (op == null || op.isEmpty() || getOperationLocked(op.mCookie) != null) {
- return 0;
- }
- pw.println(op.describe(counter));
- return 1;
- }
-
- private void dumpRecentLocked(Printer printer) {
+ public void dump(Printer printer) {
synchronized (mOperations) {
printer.println(" Most recently executed operations:");
int index = mIndex;
- if (index == 0) {
- printer.println(" <none>");
- return;
- }
-
- // Operations are dumped in order of most recent first.
- int counter = 0;
- int n = 0;
Operation operation = mOperations[index];
- do {
- printer.println(operation.describe(counter));
+ if (operation != null) {
+ // Note: SimpleDateFormat is not thread-safe, cannot be compile-time created,
+ // and is relatively expensive to create during preloading. This method is only
+ // used when dumping a connection, which is a rare (mainly error) case.
+ SimpleDateFormat opDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+ int n = 0;
+ do {
+ StringBuilder msg = new StringBuilder();
+ msg.append(" ").append(n).append(": [");
+ String formattedStartTime = opDF.format(new Date(operation.mStartWallTime));
+ msg.append(formattedStartTime);
+ msg.append("] ");
+ operation.describe(msg, false); // Never dump bingargs in a bugreport
+ printer.println(msg.toString());
- if (index > 0) {
- index -= 1;
- } else {
- index = MAX_RECENT_OPERATIONS - 1;
- }
- n++;
- counter++;
- operation = mOperations[index];
- } while (operation != null && n < MAX_RECENT_OPERATIONS);
- counter += dumpIfNotRecentLocked(printer, mTransaction, counter);
- }
- }
-
- private void dumpLongLocked(Printer printer) {
- printer.println(" Operations exceeding " + LONG_OPERATION_THRESHOLD_MS + "ms:");
- if (mLongOperations.isEmpty()) {
- printer.println(" <none>");
- return;
- }
- Operation[] longOps = mLongOperations.toArray();
- for (int i = 0; i < longOps.length; i++) {
- if (longOps[i] != null) {
- printer.println(longOps[i].describe(i));
+ if (index > 0) {
+ index -= 1;
+ } else {
+ index = MAX_RECENT_OPERATIONS - 1;
+ }
+ n += 1;
+ operation = mOperations[index];
+ } while (operation != null && n < MAX_RECENT_OPERATIONS);
+ } else {
+ printer.println(" <none>");
}
}
}
-
- public long getTotalLongOperations() {
- return mTotalLongOperations;
- }
-
- public void dump(Printer printer) {
- synchronized (mOperations) {
- dumpRecentLocked(printer);
- dumpLongLocked(printer);
- }
- }
}
- private final class Operation {
+ private static final class Operation {
// Trim all SQL statements to 256 characters inside the trace marker.
// This limit gives plenty of context while leaving space for other
// entries in the trace buffer (and ensures atrace doesn't truncate the
// marker for us, potentially losing metadata in the process).
private static final int MAX_TRACE_METHOD_NAME_LEN = 256;
- // The reserved start time that indicates the Operation is empty.
- private static final long EMPTY_OPERATION = -1;
-
- // The formatter for the timestamp.
- private static final DateTimeFormatter sDateTime =
- DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS", Locale.US);
-
public long mStartWallTime; // in System.currentTimeMillis()
public long mStartTime; // in SystemClock.uptimeMillis();
public long mEndTime; // in SystemClock.uptimeMillis();
@@ -1896,58 +1801,16 @@
public boolean mFinished;
public Exception mException;
public int mCookie;
+ public String mPath;
public long mResultLong; // MIN_VALUE means "value not set".
public String mResultString;
- /** Reset the object to begin a new operation. */
- void start() {
- mStartWallTime = System.currentTimeMillis();
- mStartTime = SystemClock.uptimeMillis();
- mEndTime = Long.MIN_VALUE;
- mKind = null;
- mSql = null;
- if (mBindArgs != null) mBindArgs.clear();
- mFinished = false;
- mException = null;
- mCookie = -1;
- mResultLong = Long.MIN_VALUE;
- mResultString = null;
- }
-
- /**
- * Initialize from the source object. This is meant to clone the object for use in a
- * transaction operation. To that end, the local bind args are set to null.
- */
- void copyFrom(Operation r) {
- mStartWallTime = r.mStartWallTime;
- mStartTime = r.mStartTime;
- mEndTime = r.mEndTime;
- mKind = r.mKind;
- mSql = r.mSql;
- mBindArgs = null;
- mFinished = r.mFinished;
- mException = r.mException;
- mCookie = r.mCookie;
- mResultLong = r.mResultLong;
- mResultString = r.mResultString;
- }
-
- /** Mark the operation empty. */
- void setEmpty() {
- mStartWallTime = EMPTY_OPERATION;
- }
-
- /** Return true if the operation is empty. */
- boolean isEmpty() {
- return mStartWallTime == EMPTY_OPERATION;
- }
-
public void describe(StringBuilder msg, boolean allowDetailedLog) {
msg.append(mKind);
if (mFinished) {
msg.append(" took ").append(mEndTime - mStartTime).append("ms");
} else {
- msg.append(" started ").append(SystemClock.uptimeMillis() - mStartTime)
+ msg.append(" started ").append(System.currentTimeMillis() - mStartWallTime)
.append("ms ago");
}
msg.append(" - ").append(getStatus());
@@ -1976,7 +1839,7 @@
}
msg.append("]");
}
- msg.append(", path=").append(mPool.getPath());
+ msg.append(", path=").append(mPath);
if (mException != null) {
msg.append(", exception=\"").append(mException.getMessage()).append("\"");
}
@@ -1988,21 +1851,6 @@
}
}
- /**
- * Convert a wall-clock time in milliseconds to logcat format.
- */
- private String timeString(long millis) {
- return sDateTime.withZone(ZoneId.systemDefault()).format(Instant.ofEpochMilli(millis));
- }
-
- public String describe(int n) {
- final StringBuilder msg = new StringBuilder();
- final String start = timeString(mStartWallTime);
- msg.append(" ").append(n).append(": [").append(start).append("] ");
- describe(msg, false); // Never dump bingargs in a bugreport
- return msg.toString();
- }
-
private String getStatus() {
if (!mFinished) {
return "running";
@@ -2016,6 +1864,7 @@
return methodName.substring(0, MAX_TRACE_METHOD_NAME_LEN);
return methodName;
}
+
}
/**
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index 15d7d66..ad335b6 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -1175,7 +1175,7 @@
+ ", isLegacyCompatibilityWalEnabled=" + isCompatibilityWalEnabled
+ ", journalMode=" + TextUtils.emptyIfNull(mConfiguration.resolveJournalMode())
+ ", syncMode=" + TextUtils.emptyIfNull(mConfiguration.resolveSyncMode()));
- printer.println(" IsReadOnlyDatabase: " + mConfiguration.isReadOnlyDatabase());
+ printer.println(" IsReadOnlyDatabase=" + mConfiguration.isReadOnlyDatabase());
if (isCompatibilityWalEnabled) {
printer.println(" Compatibility WAL enabled: wal_syncmode="
diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java
index 3b14d9d..7d9f02d 100644
--- a/core/java/android/database/sqlite/SQLiteSession.java
+++ b/core/java/android/database/sqlite/SQLiteSession.java
@@ -312,15 +312,6 @@
cancellationSignal);
}
- private String modeString(int transactionMode) {
- switch (transactionMode) {
- case TRANSACTION_MODE_IMMEDIATE: return "TRANSACTION-IMMEDIATE";
- case TRANSACTION_MODE_EXCLUSIVE: return "TRANSACTION-EXCLUSIVE";
- case TRANSACTION_MODE_DEFERRED: return "TRANSACTION-DEFERRED";
- default: return "TRANSACTION";
- }
- }
-
private void beginTransactionUnchecked(int transactionMode,
SQLiteTransactionListener transactionListener, int connectionFlags,
CancellationSignal cancellationSignal) {
@@ -330,7 +321,6 @@
if (mTransactionStack == null) {
acquireConnection(null, connectionFlags, cancellationSignal); // might throw
- mConnection.recordBeginTransaction(modeString(transactionMode));
}
try {
// Set up the transaction such that we can back out safely
@@ -475,7 +465,6 @@
mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw
}
} finally {
- mConnection.recordEndTransaction(successful);
releaseConnection(); // might throw
}
}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 0208fed..be9f0a0 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -295,7 +295,7 @@
* called.
*
* @param view The customized view information.
- * @return This builder.re
+ * @return This builder.
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
@NonNull
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 991bade..ec9b013 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -585,6 +585,11 @@
* <p>Configuring a session with an empty or null list will close the current session, if
* any. This can be used to release the current session's target surfaces for another use.</p>
*
+ * <p>This function throws an {@code IllegalArgumentException} if called with a
+ * SessionConfiguration lacking state callbacks or valid output surfaces. The only exceptions
+ * are deferred SurfaceView or SurfaceTexture outputs. See {@link
+ * OutputConfiguration#OutputConfiguration(Size, Class)} for details.</p>
+ *
* <h3>Regular capture</h3>
*
* <p>While any of the sizes from {@link StreamConfigurationMap#getOutputSizes} can be used when
@@ -1675,19 +1680,32 @@
*
* <p><b>IMPORTANT:</b></p>
* <ul>
- * <li>If a feature support can be queried via
+ * <li>If feature support can be queried via
* {@link CameraCharacteristics#SCALER_MANDATORY_STREAM_COMBINATIONS} or
* {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP}, applications should
- * directly use it rather than calling this function as: (1) using
- * {@code CameraCharacteristics} is more efficient, and (2) calling this function with on
- * non-supported devices will throw a {@link UnsupportedOperationException}.
+ * directly use that route rather than calling this function as: (1) using
+ * {@code CameraCharacteristics} is more efficient, and (2) calling this function with
+ * certain non-supported features will throw a {@link IllegalArgumentException}.</li>
*
- * <li>To minimize latency of {@link SessionConfiguration} creation, applications can
- * use deferred surfaces for SurfaceView and SurfaceTexture to avoid waiting for UI
- * creation before setting up the camera. For {@link android.media.MediaRecorder} and
- * {@link android.media.MediaCodec} uses, applications can use {@code ImageReader} with
- * {@link android.hardware.HardwareBuffer#USAGE_VIDEO_ENCODE}. The lightweight nature of
- * {@code ImageReader} helps minimize the latency cost.
+ * <li>To minimize {@link SessionConfiguration} creation latency due to its dependency on
+ * output surfaces, the application can call this method before acquiring valid
+ * {@link android.view.SurfaceView}, {@link android.graphics.SurfaceTexture},
+ * {@link android.media.MediaRecorder}, {@link android.media.MediaCodec}, or {@link
+ * android.media.ImageReader} surfaces. For {@link android.view.SurfaceView},
+ * {@link android.graphics.SurfaceTexture}, {@link android.media.MediaRecorder}, and
+ * {@link android.media.MediaCodec}, the application can call
+ * {@link OutputConfiguration#OutputConfiguration(Size, Class)}. For {@link
+ * android.media.ImageReader}, the application can call {@link
+ * OutputConfiguration#OutputConfiguration(int, Size)}, {@link
+ * OutputConfiguration#OutputConfiguration(int, int, Size)}, {@link
+ * OutputConfiguration#OutputConfiguration(int, Size, long)}, or {@link
+ * OutputConfiguration#OutputConfiguration(int, int, Size, long)}. The {@link
+ * SessionConfiguration} can then be created using the OutputConfiguration objects and
+ * be used to query whether it's supported by the camera device. To create the
+ * CameraCaptureSession, the application still needs to make sure all output surfaces
+ * are added via {@link OutputConfiguration#addSurfaces} with the exception of deferred
+ * surfaces for {@link android.view.SurfaceView} and
+ * {@link android.graphics.SurfaceTexture}.</li>
* </ul>
*
* @return {@code true} if the given session configuration is supported by the camera
@@ -1706,8 +1724,8 @@
@NonNull SessionConfiguration config) throws CameraAccessException;
/**
- * <p>Get camera characteristics for a particular session configuration for this camera
- * device</p>
+ * Get camera characteristics for a particular session configuration for this camera
+ * device.
*
* <p>The camera characteristics returned by this method are different from those returned
* from {@link CameraManager#getCameraCharacteristics}. The characteristics returned here
@@ -1718,6 +1736,24 @@
* <p>Other than that, the characteristics returned here can be used in the same way as
* those returned from {@link CameraManager#getCameraCharacteristics}.</p>
*
+ * <p>To optimize latency, the application can call this method before acquiring valid
+ * {@link android.view.SurfaceView}, {@link android.graphics.SurfaceTexture},
+ * {@link android.media.MediaRecorder}, {@link android.media.MediaCodec}, or {@link
+ * android.media.ImageReader} surfaces. For {@link android.view.SurfaceView},
+ * {@link android.graphics.SurfaceTexture}, {@link android.media.MediaRecorder}, and
+ * {@link android.media.MediaCodec}, the application can call
+ * {@link OutputConfiguration#OutputConfiguration(Size, Class)}. For {@link
+ * android.media.ImageReader}, the application can call {@link
+ * OutputConfiguration#OutputConfiguration(int, Size)}, {@link
+ * OutputConfiguration#OutputConfiguration(int, int, Size)}, {@link
+ * OutputConfiguration#OutputConfiguration(int, Size, long)}, or {@link
+ * OutputConfiguration#OutputConfiguration(int, int, Size, long)}. The {@link
+ * SessionConfiguration} can then be created using the OutputConfiguration objects and
+ * be used for this function. To create the CameraCaptureSession, the application still
+ * needs to make sure all output surfaces are added via {@link
+ * OutputConfiguration#addSurfaces} with the exception of deferred surfaces for {@link
+ * android.view.SurfaceView} and {@link android.graphics.SurfaceTexture}.</p>
+ *
* @param sessionConfig The session configuration for which characteristics are fetched.
* @return CameraCharacteristics specific to a given session configuration.
*
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 85f9900..eb644e8 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -227,14 +227,19 @@
private static List<Size> generateSupportedSizes(List<SizeList> sizesList,
Integer format,
StreamConfigurationMap streamMap) {
- // Per API contract it is assumed that the extension is able to support all
- // camera advertised sizes for a given format in case it doesn't return
- // a valid non-empty size list.
ArrayList<Size> ret = getSupportedSizes(sizesList, format);
- Size[] supportedSizes = streamMap.getOutputSizes(format);
- if ((ret.isEmpty()) && (supportedSizes != null)) {
- ret.addAll(Arrays.asList(supportedSizes));
+
+ if (format == ImageFormat.JPEG || format == ImageFormat.YUV_420_888 ||
+ format == ImageFormat.PRIVATE) {
+ // Per API contract it is assumed that the extension is able to support all
+ // camera advertised sizes for JPEG, YUV_420_888 and PRIVATE in case it doesn't return
+ // a valid non-empty size list.
+ Size[] supportedSizes = streamMap.getOutputSizes(format);
+ if ((ret.isEmpty()) && (supportedSizes != null)) {
+ ret.addAll(Arrays.asList(supportedSizes));
+ }
}
+
return ret;
}
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index d2e4a61..b43a900 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -47,6 +47,7 @@
import android.hardware.camera2.utils.CameraIdAndSessionConfiguration;
import android.hardware.camera2.utils.ConcurrentCameraIdCombination;
import android.hardware.camera2.utils.ExceptionUtils;
+import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.DisplayManager;
import android.os.Binder;
@@ -204,14 +205,12 @@
mDeviceStateListeners.add(new WeakReference<>(listener));
}
+ @SuppressWarnings("FlaggedApi")
@Override
- public final void onBaseStateChanged(int state) {
- handleStateChange(state);
- }
-
- @Override
- public final void onStateChanged(int state) {
- handleStateChange(state);
+ public void onDeviceStateChanged(DeviceState state) {
+ // Suppressing the FlaggedAPI warning as this specific API isn't new, just moved to
+ // system API which requires it to be flagged.
+ handleStateChange(state.getIdentifier());
}
}
diff --git a/core/java/android/hardware/camera2/MultiResolutionImageReader.java b/core/java/android/hardware/camera2/MultiResolutionImageReader.java
index 8a18a0d..116928b 100644
--- a/core/java/android/hardware/camera2/MultiResolutionImageReader.java
+++ b/core/java/android/hardware/camera2/MultiResolutionImageReader.java
@@ -16,6 +16,8 @@
package android.hardware.camera2;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntRange;
@@ -26,20 +28,15 @@
import android.graphics.ImageFormat.Format;
import android.hardware.HardwareBuffer;
import android.hardware.HardwareBuffer.Usage;
+import android.hardware.camera2.params.MultiResolutionStreamInfo;
import android.media.Image;
import android.media.ImageReader;
-import android.hardware.camera2.params.MultiResolutionStreamInfo;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
+import android.util.Size;
import android.view.Surface;
import com.android.internal.camera.flags.Flags;
-import java.nio.NioUtils;
-import java.util.ArrayList;
import java.util.Collection;
-import java.util.List;
import java.util.concurrent.Executor;
/**
@@ -351,6 +348,52 @@
}
/**
+ * Get the internal ImageReader surface based on configured size and physical camera Id.
+ *
+ * <p>The {@code configuredSize} and {@code physicalCameraId} parameters must match one of the
+ * MultiResolutionStreamInfo used to create this {@link MultiResolutionImageReader}.</p>
+ *
+ * <p>The Surface returned from this function isn't meant to be used directly as part of a
+ * {@link CaptureRequest}. It should instead be used for creating an OutputConfiguration
+ * before session creation. See {@link OutputConfiguration#setSurfacesForMultiResolutionOutput}
+ * for details. For {@link CaptureRequest}, use {@link #getSurface()} instead.</p>
+ *
+ * <p>Please note that holding on to the Surface objects returned by this method is not enough
+ * to keep their parent MultiResolutionImageReaders from being reclaimed. In that sense, a
+ * Surface acts like a {@link java.lang.ref.WeakReference weak reference} to the
+ * MultiResolutionImageReader that provides it.</p>
+ *
+ * @param configuredSize The configured size corresponding to one of the internal ImageReader.
+ * @param physicalCameraId The physical camera Id the internal ImageReader targets for. If
+ * the ImageReader is not targeting a physical camera of a logical
+ * multi-camera, this parameter is set to "".
+ *
+ * @return The {@link Surface} of the internal ImageReader corresponding to the provided
+ * configured size and physical camera Id.
+ *
+ * @throws IllegalArgumentException If {@code configuredSize} is {@code null}, or the ({@code
+ * configuredSize} and {@code physicalCameraId}) combo is not
+ * part of this {@code MultiResolutionImageReader}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP)
+ public @NonNull Surface getSurface(@NonNull Size configuredSize,
+ @NonNull String physicalCameraId) {
+ checkNotNull(configuredSize, "configuredSize must not be null");
+ checkNotNull(physicalCameraId, "physicalCameraId must not be null");
+
+ for (int i = 0; i < mStreamInfo.length; i++) {
+ if (mStreamInfo[i].getWidth() == configuredSize.getWidth()
+ && mStreamInfo[i].getHeight() == configuredSize.getHeight()
+ && physicalCameraId.equals(mStreamInfo[i].getPhysicalCameraId())) {
+ return mReaders[i].getSurface();
+ }
+ }
+ throw new IllegalArgumentException("configuredSize and physicalCameraId don't match with "
+ + "this MultiResolutionImageReader");
+ }
+
+ /**
* Get the surface that is used as a target for {@link CaptureRequest}
*
* <p>The application must use the surface returned by this function as a target for
diff --git a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
index c4f653c..9d46b55 100644
--- a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
+++ b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
@@ -25,5 +25,5 @@
CameraMetadataNative sessionParameter;
int sessionTemplateId;
int sessionType;
- int colorSpace;
+ int colorSpace = -1;
}
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 5b7f8bb..6d9b51cb 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -348,7 +348,23 @@
cameraOutput.setTimestampBase(OutputConfiguration.TIMESTAMP_BASE_SENSOR);
cameraOutput.setReadoutTimestampEnabled(false);
cameraOutput.setPhysicalCameraId(output.physicalCameraId);
- cameraOutput.setDynamicRangeProfile(output.dynamicRangeProfile);
+ if (Flags.extension10Bit()) {
+ boolean validDynamicRangeProfile = false;
+ for (long profile = DynamicRangeProfiles.STANDARD;
+ profile < DynamicRangeProfiles.PUBLIC_MAX; profile <<= 1) {
+ if (output.dynamicRangeProfile == profile) {
+ validDynamicRangeProfile = true;
+ break;
+ }
+ }
+ if (validDynamicRangeProfile) {
+ cameraOutput.setDynamicRangeProfile(output.dynamicRangeProfile);
+ } else {
+ Log.e(TAG, "Extension configured dynamic range profile "
+ + output.dynamicRangeProfile
+ + " is not valid, using default DynamicRangeProfile.STANDARD");
+ }
+ }
outputList.add(cameraOutput);
mCameraConfigMap.put(cameraOutput.getSurface(), output);
}
@@ -363,9 +379,15 @@
SessionConfiguration sessionConfiguration = new SessionConfiguration(sessionType,
outputList, new CameraExtensionUtils.HandlerExecutor(mHandler),
new SessionStateHandler());
- if (sessionConfig.colorSpace != ColorSpaceProfiles.UNSPECIFIED) {
- sessionConfiguration.setColorSpace(
- ColorSpace.Named.values()[sessionConfig.colorSpace]);
+ if (Flags.extension10Bit()) {
+ if (sessionConfig.colorSpace >= 0
+ && sessionConfig.colorSpace < ColorSpace.Named.values().length) {
+ sessionConfiguration.setColorSpace(
+ ColorSpace.Named.values()[sessionConfig.colorSpace]);
+ } else {
+ Log.e(TAG, "Extension configured color space " + sessionConfig.colorSpace
+ + " is not valid, using default unspecified color space");
+ }
}
if ((sessionConfig.sessionParameter != null) &&
(!sessionConfig.sessionParameter.isEmpty())) {
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
index 0f199b1..9e01438 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
@@ -90,7 +90,6 @@
@Override
public boolean isSessionConfigurationSupported(@NonNull SessionConfiguration config)
throws CameraAccessException {
- // TODO(b/298033056): restructure the OutputConfiguration API for better usability
synchronized (mInterfaceLock) {
if (mCameraManager.isCameraServiceDisabled()) {
throw new IllegalArgumentException("No cameras available on device");
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 8aacd5e..dda52dd 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -19,6 +19,7 @@
import static com.android.internal.util.Preconditions.*;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -27,6 +28,9 @@
import android.annotation.TestApi;
import android.graphics.ColorSpace;
import android.graphics.ImageFormat;
+import android.graphics.ImageFormat.Format;
+import android.hardware.HardwareBuffer;
+import android.hardware.HardwareBuffer.Usage;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
@@ -43,6 +47,8 @@
import android.util.Size;
import android.view.Surface;
+import com.android.internal.camera.flags.Flags;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -50,6 +56,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* A class for describing camera output, which contains a {@link Surface} and its specific
@@ -361,6 +368,21 @@
private final int SURFACE_TYPE_SURFACE_TEXTURE = 1;
/**
+ * The surface is obtained from {@link android.media.MediaRecorder}.
+ */
+ private static final int SURFACE_TYPE_MEDIA_RECORDER = 2;
+
+ /**
+ * The surface is obtained from {@link android.media.MediaCodec}.
+ */
+ private static final int SURFACE_TYPE_MEDIA_CODEC = 3;
+
+ /**
+ * The surface is obtained from {@link android.media.ImageReader}.
+ */
+ private static final int SURFACE_TYPE_IMAGE_READER = 4;
+
+ /**
* Maximum number of surfaces supported by one {@link OutputConfiguration}.
*
* <p>The combined number of surfaces added by the constructor and
@@ -576,6 +598,7 @@
mMirrorMode = MIRROR_MODE_AUTO;
mReadoutTimestampEnabled = false;
mIsReadoutSensorTimestampBase = false;
+ mUsage = 0;
}
/**
@@ -592,13 +615,7 @@
@NonNull MultiResolutionImageReader multiResolutionImageReader) {
checkNotNull(multiResolutionImageReader, "Multi-resolution image reader must not be null");
- int groupId = MULTI_RESOLUTION_GROUP_ID_COUNTER;
- MULTI_RESOLUTION_GROUP_ID_COUNTER++;
- // Skip in case the group id counter overflows to -1, the invalid value.
- if (MULTI_RESOLUTION_GROUP_ID_COUNTER == -1) {
- MULTI_RESOLUTION_GROUP_ID_COUNTER++;
- }
-
+ int groupId = getAndIncreaseMultiResolutionGroupId();
ImageReader[] imageReaders = multiResolutionImageReader.getReaders();
ArrayList<OutputConfiguration> configs = new ArrayList<OutputConfiguration>();
for (int i = 0; i < imageReaders.length; i++) {
@@ -620,6 +637,115 @@
}
/**
+ * Create a list of {@link OutputConfiguration} instances for a
+ * {@link android.hardware.camera2.params.MultiResolutionImageReader}.
+ *
+ * <p>This method can be used to create query OutputConfigurations for a
+ * MultiResolutionImageReader that can be included in a SessionConfiguration passed into
+ * {@link CameraDeviceSetup#isSessionConfigurationSupported} before opening and setting up
+ * a camera device in full, at which point {@link #setSurfacesForMultiResolutionOutput}
+ * can be used to link to the actual MultiResolutionImageReader.</p>
+ *
+ * <p>This constructor takes same arguments used to create a {@link
+ * MultiResolutionImageReader}: a collection of {@link MultiResolutionStreamInfo}
+ * objects and the format.</p>
+ *
+ * @param streams The group of multi-resolution stream info objects, which are used to create a
+ * multi-resolution image reader containing a number of ImageReaders.
+ * @param format The format of the MultiResolutionImageReader. This must be one of the {@link
+ * android.graphics.ImageFormat} or {@link android.graphics.PixelFormat} constants
+ * supported by the camera device. Note that not all formats are supported, like
+ * {@link ImageFormat.NV21}. The supported multi-resolution reader format can be
+ * queried by {@link MultiResolutionStreamConfigurationMap#getOutputFormats}.
+ *
+ * @return The list of {@link OutputConfiguration} objects for a MultiResolutionImageReader.
+ *
+ * @throws IllegaArgumentException If the {@code streams} is null or doesn't contain
+ * at least 2 items, or if {@code format} isn't a valid camera
+ * format.
+ *
+ * @see MultiResolutionImageReader
+ * @see MultiResolutionStreamInfo
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP)
+ public static @NonNull List<OutputConfiguration> createInstancesForMultiResolutionOutput(
+ @NonNull Collection<MultiResolutionStreamInfo> streams,
+ @Format int format) {
+ if (streams == null || streams.size() <= 1) {
+ throw new IllegalArgumentException(
+ "The streams list must contain at least 2 entries");
+ }
+ if (format == ImageFormat.NV21) {
+ throw new IllegalArgumentException(
+ "NV21 format is not supported");
+ }
+
+ int groupId = getAndIncreaseMultiResolutionGroupId();
+ ArrayList<OutputConfiguration> configs = new ArrayList<OutputConfiguration>();
+ for (MultiResolutionStreamInfo stream : streams) {
+ Size surfaceSize = new Size(stream.getWidth(), stream.getHeight());
+ OutputConfiguration config = new OutputConfiguration(
+ groupId, format, surfaceSize);
+ config.setPhysicalCameraId(stream.getPhysicalCameraId());
+ config.setMultiResolutionOutput();
+ configs.add(config);
+
+ // No need to call addSensorPixelModeUsed for ultra high resolution sensor camera,
+ // because regular and max resolution output configurations are used for DEFAULT mode
+ // and MAX_RESOLUTION mode respectively by default.
+ }
+
+ return configs;
+ }
+
+ /**
+ * Set the OutputConfiguration surfaces corresponding to the {@link MultiResolutionImageReader}.
+ *
+ * <p>This function should be used together with {@link
+ * #createInstancesForMultiResolutionOutput}. The application calls {@link
+ * #createInstancesForMultiResolutionOutput} first to create a list of
+ * OutputConfiguration objects without the actual MultiResolutionImageReader.
+ * Once the MultiResolutionImageReader is created later during full camera setup, the
+ * application then calls this function to assign the surfaces to the OutputConfiguration
+ * instances.</p>
+ *
+ * @param outputConfigurations The OutputConfiguration objects created by {@link
+ * #createInstancesFromMultiResolutionOutput}
+ * @param multiResolutionImageReader The MultiResolutionImageReader object created from the same
+ * MultiResolutionStreamInfo parameters as
+ * {@code outputConfigurations}.
+ * @throws IllegalArgumentException If {@code outputConfigurations} or {@code
+ * multiResolutionImageReader} is {@code null}, the {@code
+ * outputConfigurations} and {@code multiResolutionImageReader}
+ * sizes don't match, or if the
+ * {@code multiResolutionImageReader}'s surfaces don't match
+ * with the {@code outputConfigurations}.
+ * @throws IllegalStateException If {@code outputConfigurations} already contains valid output
+ * surfaces.
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP)
+ public static void setSurfacesForMultiResolutionOutput(
+ @NonNull Collection<OutputConfiguration> outputConfigurations,
+ @NonNull MultiResolutionImageReader multiResolutionImageReader) {
+ checkNotNull(outputConfigurations, "outputConfigurations must not be null");
+ checkNotNull(multiResolutionImageReader, "multiResolutionImageReader must not be null");
+ if (outputConfigurations.size() != multiResolutionImageReader.getReaders().length) {
+ throw new IllegalArgumentException(
+ "outputConfigurations and multiResolutionImageReader sizes must match");
+ }
+
+ for (OutputConfiguration config : outputConfigurations) {
+ String physicalCameraId = config.getPhysicalCameraId();
+ if (physicalCameraId == null) {
+ physicalCameraId = "";
+ }
+ Surface surface = multiResolutionImageReader.getSurface(config.getConfiguredSize(),
+ physicalCameraId);
+ config.addSurface(surface);
+ }
+ }
+
+ /**
* Create a new {@link OutputConfiguration} instance, with desired Surface size and Surface
* source class.
* <p>
@@ -628,30 +754,64 @@
* with a deferred Surface. The application can use this output configuration to create a
* session.
* </p>
- * <p>
- * However, the actual output Surface must be set via {@link #addSurface} and the deferred
- * Surface configuration must be finalized via {@link
- * CameraCaptureSession#finalizeOutputConfigurations} before submitting a request with this
- * Surface target. The deferred Surface can only be obtained either from {@link
- * android.view.SurfaceView} by calling {@link android.view.SurfaceHolder#getSurface}, or from
+ *
+ * <p>Starting from {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM Android V},
+ * the deferred Surface can be obtained: (1) from {@link android.view.SurfaceView}
+ * by calling {@link android.view.SurfaceHolder#getSurface}, (2) from
* {@link android.graphics.SurfaceTexture} via
- * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}).
- * </p>
+ * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}, (3) from {@link
+ * android.media.MediaRecorder} via {@link android.media.MediaRecorder.getSurface} or {@link
+ * android.media.MediaCodec#createPersistentInputSurface}, or (4) from {@link
+ * android.media.MediaCodce} via {@link android.media.MediaCodec#createInputSurface} or {@link
+ * android.media.MediaCodec#createPersistentInputSource}.</p>
+ *
+ * <ul>
+ * <li>Surfaces for {@link android.view.SurfaceView} and {@link android.graphics.SurfaceTexture}
+ * can be deferred until after {@link CameraDevice#createCaptureSession}. In that case, the
+ * output Surface must be set via {@link #addSurface}, and the Surface configuration must be
+ * finalized via {@link CameraCaptureSession#finalizeOutputConfiguration} before submitting
+ * a request with the Surface target.</li>
+ * <li>For all other target types, the output Surface must be set by {@link #addSurface},
+ * and {@link CameraCaptureSession#finalizeOutputConfiguration} is not needed because the
+ * OutputConfiguration used to create the session will contain the actual Surface.</li>
+ * </ul>
+ *
+ * <p>Before {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM Android V}, only {@link
+ * android.view.SurfaceView} and {@link android.graphics.SurfaceTexture} are supported. Both
+ * kind of outputs can be deferred until after {@link
+ * CameraDevice#createCaptureSessionByOutputConfiguration}.</p>
+ *
+ * <p>An OutputConfiguration object created by this constructor can be used for {@link
+ * CameraDeviceSetup.isSessionConfigurationSupported} and {@link
+ * CameraDeviceSetup.getSessionCharacteristics} without having called {@link #addSurface}.</p>
*
* @param surfaceSize Size for the deferred surface.
* @param klass a non-{@code null} {@link Class} object reference that indicates the source of
- * this surface. Only {@link android.view.SurfaceHolder SurfaceHolder.class} and
- * {@link android.graphics.SurfaceTexture SurfaceTexture.class} are supported.
+ * this surface. Only {@link android.view.SurfaceHolder SurfaceHolder.class},
+ * {@link android.graphics.SurfaceTexture SurfaceTexture.class}, {@link
+ * android.media.MediaRecorder MediaRecorder.class}, and
+ * {@link android.media.MediaCodec MediaCodec.class} are supported.
+ * Before {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM Android V}, only
+ * {@link android.view.SurfaceHolder SurfaceHolder.class} and {@link
+ * android.graphics.SurfaceTexture SurfaceTexture.class} are supported.
* @throws IllegalArgumentException if the Surface source class is not supported, or Surface
* size is zero.
*/
public <T> OutputConfiguration(@NonNull Size surfaceSize, @NonNull Class<T> klass) {
- checkNotNull(klass, "surfaceSize must not be null");
+ checkNotNull(surfaceSize, "surfaceSize must not be null");
checkNotNull(klass, "klass must not be null");
if (klass == android.view.SurfaceHolder.class) {
mSurfaceType = SURFACE_TYPE_SURFACE_VIEW;
+ mIsDeferredConfig = true;
} else if (klass == android.graphics.SurfaceTexture.class) {
mSurfaceType = SURFACE_TYPE_SURFACE_TEXTURE;
+ mIsDeferredConfig = true;
+ } else if (klass == android.media.MediaRecorder.class) {
+ mSurfaceType = SURFACE_TYPE_MEDIA_RECORDER;
+ mIsDeferredConfig = false;
+ } else if (klass == android.media.MediaCodec.class) {
+ mSurfaceType = SURFACE_TYPE_MEDIA_CODEC;
+ mIsDeferredConfig = false;
} else {
mSurfaceType = SURFACE_TYPE_UNKNOWN;
throw new IllegalArgumentException("Unknown surface source class type");
@@ -668,7 +828,6 @@
mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(ImageFormat.PRIVATE);
mConfiguredDataspace = StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE);
mConfiguredGenerationId = 0;
- mIsDeferredConfig = true;
mIsShared = false;
mPhysicalCameraId = null;
mIsMultiResolution = false;
@@ -678,6 +837,131 @@
mStreamUseCase = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT;
mReadoutTimestampEnabled = false;
mIsReadoutSensorTimestampBase = false;
+ mUsage = 0;
+ }
+
+ /**
+ * Create a new {@link OutputConfiguration} instance for an {@link ImageReader} for a given
+ * format and size.
+ *
+ * <p>This constructor creates an OutputConfiguration for an ImageReader without providing
+ * the actual output Surface. The actual output Surface must be set via {@link #addSurface}
+ * before creating the capture session.</p>
+ *
+ * <p>An OutputConfiguration object created by this constructor can be used for {@link
+ * CameraDeviceSetup.isSessionConfigurationSupported} and {@link
+ * CameraDeviceSetup.getSessionCharacteristics} without having called {@link #addSurface}.</p>
+ *
+ * @param format The format of the ImageReader output. This must be one of the
+ * {@link android.graphics.ImageFormat} or {@link android.graphics.PixelFormat}
+ * constants. Note that not all formats are supported by the camera device.
+ * @param surfaceSize Size for the ImageReader surface.
+ * @throws IllegalArgumentException if the Surface size is null or zero.
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP)
+ public OutputConfiguration(@Format int format, @NonNull Size surfaceSize) {
+ this(format, surfaceSize,
+ format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN);
+ }
+
+ /**
+ * Create a new {@link OutputConfiguration} instance for an {@link ImageReader} for a given
+ * surfaceGroupId, format, and size.
+ *
+ * <p>This constructor creates an OutputConfiguration for an ImageReader without providing
+ * the actual output Surface. The actual output Surface must be set via {@link #addSurface}
+ * before creating the capture session.</p>
+ *
+ * <p>An OutputConfiguration object created by this constructor can be used for {@link
+ * CameraDeviceSetup.isSessionConfigurationSupported} and {@link
+ * CameraDeviceSetup.getSessionCharacteristics} without having called {@link #addSurface}.</p>
+ *
+ * @param surfaceGroupId A group ID for this output, used for sharing memory between multiple
+ * outputs.
+ * @param format The format of the ImageReader output. This must be one of the
+ * {@link android.graphics.ImageFormat} or {@link android.graphics.PixelFormat}
+ * constants. Note that not all formats are supported by the camera device.
+ * @param surfaceSize Size for the ImageReader surface.
+ * @throws IllegalArgumentException if the Surface size is null or zero.
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP)
+ public OutputConfiguration(int surfaceGroupId, @Format int format, @NonNull Size surfaceSize) {
+ this(surfaceGroupId, format, surfaceSize,
+ format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN);
+ }
+
+ /**
+ * Create a new {@link OutputConfiguration} instance for an {@link ImageReader} for a given
+ * format, size, and usage flags.
+ *
+ * <p>This constructor creates an OutputConfiguration for an ImageReader without providing
+ * the actual output Surface. The actual output Surface must be set via {@link #addSurface}
+ * before creating the capture session.</p>
+ *
+ * <p>An OutputConfiguration object created by this constructor can be used for {@link
+ * CameraDeviceSetup.isSessionConfigurationSupported} and {@link
+ * CameraDeviceSetup.getSessionCharacteristics} without having called {@link #addSurface}.</p>
+ *
+ * @param format The format of the ImageReader output. This must be one of the
+ * {@link android.graphics.ImageFormat} or {@link android.graphics.PixelFormat}
+ * constants. Note that not all formats are supported by the camera device.
+ * @param surfaceSize Size for the ImageReader surface.
+ * @param usage The usage flags of the ImageReader output surface.
+ * @throws IllegalArgumentException if the Surface size is null or zero.
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP)
+ public OutputConfiguration(@Format int format, @NonNull Size surfaceSize, @Usage long usage) {
+ this(SURFACE_GROUP_ID_NONE, format, surfaceSize, usage);
+ }
+
+ /**
+ * Create a new {@link OutputConfiguration} instance for an {@link ImageReader} for a given
+ * surface group id, format, size, and usage flags.
+ *
+ * <p>This constructor creates an OutputConfiguration for an ImageReader without providing
+ * the actual output Surface. The actual output Surface must be set via {@link #addSurface}
+ * before creating the capture session.</p>
+ *
+ * <p>An OutputConfiguration object created by this constructor can be used for {@link
+ * CameraDeviceSetup.isSessionConfigurationSupported} and {@link
+ * CameraDeviceSetup.getSessionCharacteristics} without having called {@link #addSurface}.</p>
+ *
+ * @param surfaceGroupId A group ID for this output, used for sharing memory between multiple
+ * outputs.
+ * @param format The format of the ImageReader output. This must be one of the
+ * {@link android.graphics.ImageFormat} or {@link android.graphics.PixelFormat}
+ * constants. Note that not all formats are supported by the camera device.
+ * @param surfaceSize Size for the ImageReader surface.
+ * @param usage The usage flags of the ImageReader output surface.
+ * @throws IllegalArgumentException if the Surface size is null or zero.
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP)
+ public OutputConfiguration(int surfaceGroupId, @Format int format,
+ @NonNull Size surfaceSize, @Usage long usage) {
+ checkNotNull(surfaceSize, "surfaceSize must not be null");
+ if (surfaceSize.getWidth() == 0 || surfaceSize.getHeight() == 0) {
+ throw new IllegalArgumentException("Surface size needs to be non-zero");
+ }
+
+ mSurfaceType = SURFACE_TYPE_IMAGE_READER;
+ mSurfaceGroupId = surfaceGroupId;
+ mSurfaces = new ArrayList<Surface>();
+ mRotation = ROTATION_0;
+ mConfiguredSize = surfaceSize;
+ mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(format);
+ mConfiguredDataspace = StreamConfigurationMap.imageFormatToDataspace(format);
+ mConfiguredGenerationId = 0;
+ mIsDeferredConfig = false;
+ mIsShared = false;
+ mPhysicalCameraId = null;
+ mIsMultiResolution = false;
+ mSensorPixelModesUsed = new ArrayList<Integer>();
+ mDynamicRangeProfile = DynamicRangeProfiles.STANDARD;
+ mColorSpace = ColorSpaceProfiles.UNSPECIFIED;
+ mStreamUseCase = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT;
+ mReadoutTimestampEnabled = false;
+ mIsReadoutSensorTimestampBase = false;
+ mUsage = usage;
}
/**
@@ -852,7 +1136,8 @@
/**
* Check if this configuration has deferred configuration.
*
- * <p>This will return true if the output configuration was constructed with surface deferred by
+ * <p>This will return true if the output configuration was constructed with {@link
+ * android.view.SurfaceView} or {@link android.graphics.SurfaceTexture} deferred by
* {@link OutputConfiguration#OutputConfiguration(Size, Class)}. It will return true even after
* the deferred surface is added later by {@link OutputConfiguration#addSurface}.</p>
*
@@ -872,13 +1157,28 @@
* {@link CameraCaptureSession#finalizeOutputConfigurations}. It is possible to call this method
* after the output configurations have been finalized only in cases of enabled surface sharing
* see {@link #enableSurfaceSharing}. The modified output configuration must be updated with
- * {@link CameraCaptureSession#updateOutputConfiguration}.</p>
+ * {@link CameraCaptureSession#updateOutputConfiguration}. If this function is called before
+ * session creation, {@link CameraCaptureSession#finalizeOutputConfigurations} doesn't need to
+ * be called.</p>
*
- * <p> If the OutputConfiguration was constructed with a deferred surface by {@link
- * OutputConfiguration#OutputConfiguration(Size, Class)}, the added surface must be obtained
- * from {@link android.view.SurfaceView} by calling {@link android.view.SurfaceHolder#getSurface},
- * or from {@link android.graphics.SurfaceTexture} via
- * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}).</p>
+ * <p> If the OutputConfiguration was constructed by {@link
+ * OutputConfiguration#OutputConfiguration(Size, Class)}, the added surface must be obtained:
+ * <ul>
+ * <li>from {@link android.view.SurfaceView} by calling
+ * {@link android.view.SurfaceHolder#getSurface}</li>
+ * <li>from {@link android.graphics.SurfaceTexture} by calling
+ * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}</li>
+ * <li>from {@link android.media.MediaRecorder} by calling
+ * {@link android.media.MediaRecorder#getSurface} or {@link
+ * android.media.MediaCodec#createPersistentInputSurface}</li>
+ * <li>from {@link android.media.MediaCodce} by calling
+ * {@link android.media.MediaCodec#createInputSurface} or {@link
+ * android.media.MediaCodec#createPersistentInputSource}</li>
+ * </ul>
+ *
+ * <p> If the OutputConfiguration was constructed by {@link #OutputConfiguration(int, Size)}
+ * or its variants, the added surface must be obtained from {@link android.media.ImageReader}
+ * by calling {@link android.media.ImageReader#getSurface}.</p>
*
* <p> If the OutputConfiguration was constructed by other constructors, the added
* surface must be compatible with the existing surface. See {@link #enableSurfaceSharing} for
@@ -934,9 +1234,13 @@
*
* <p> Surfaces added via calls to {@link #addSurface} can also be removed from the
* OutputConfiguration. The only notable exception is the surface associated with
- * the OutputConfiguration see {@link #getSurface} which was passed as part of the constructor
- * or was added first in the deferred case
- * {@link OutputConfiguration#OutputConfiguration(Size, Class)}.</p>
+ * the OutputConfiguration (see {@link #getSurface}) which was passed as part of the
+ * constructor or was added first in the case of
+ * {@link OutputConfiguration#OutputConfiguration(Size, Class)}, {@link
+ * OutputConfiguration#OutputConfiguration(int, Size)}, {@link
+ * OutputConfiguration#OutputConfiguration(int, Size, long)}, {@link
+ * OutputConfiguration#OutputConfiguration(int, int, Size)}, {@link
+ * OutputConfiguration#OutputConfiguration(int, int, Size, long)}.</p>
*
* @param surface The surface to be removed.
*
@@ -945,6 +1249,7 @@
* with {@link #addSurface}.
*/
public void removeSurface(@NonNull Surface surface) {
+ checkNotNull(surface, "Surface must not be null");
if (getSurface() == surface) {
throw new IllegalArgumentException(
"Cannot remove surface associated with this output configuration");
@@ -1175,6 +1480,7 @@
this.mTimestampBase = other.mTimestampBase;
this.mMirrorMode = other.mMirrorMode;
this.mReadoutTimestampEnabled = other.mReadoutTimestampEnabled;
+ this.mUsage = other.mUsage;
}
/**
@@ -1203,6 +1509,9 @@
int timestampBase = source.readInt();
int mirrorMode = source.readInt();
boolean readoutTimestampEnabled = source.readInt() == 1;
+ int format = source.readInt();
+ int dataSpace = source.readInt();
+ long usage = source.readLong();
mSurfaceGroupId = surfaceSetId;
mRotation = rotation;
@@ -1211,6 +1520,7 @@
mIsDeferredConfig = isDeferred;
mIsShared = isShared;
mSurfaces = surfaces;
+ mUsage = 0;
if (mSurfaces.size() > 0) {
mSurfaceType = SURFACE_TYPE_UNKNOWN;
mConfiguredFormat = SurfaceUtils.getSurfaceFormat(mSurfaces.get(0));
@@ -1218,9 +1528,16 @@
mConfiguredGenerationId = mSurfaces.get(0).getGenerationId();
} else {
mSurfaceType = surfaceType;
- mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(ImageFormat.PRIVATE);
- mConfiguredDataspace =
- StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE);
+ if (mSurfaceType != SURFACE_TYPE_IMAGE_READER) {
+ mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(
+ ImageFormat.PRIVATE);
+ mConfiguredDataspace =
+ StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE);
+ } else {
+ mConfiguredFormat = format;
+ mConfiguredDataspace = dataSpace;
+ mUsage = usage;
+ }
mConfiguredGenerationId = 0;
}
mPhysicalCameraId = physicalCameraId;
@@ -1293,6 +1610,31 @@
return mSurfaceGroupId;
}
+ /**
+ * Get the configured size associated with this {@link OutputConfiguration}.
+ *
+ * @return The configured size associated with this {@link OutputConfiguration}.
+ *
+ * @hide
+ */
+ public Size getConfiguredSize() {
+ return mConfiguredSize;
+ }
+
+ /**
+ * Get the physical camera ID associated with this {@link OutputConfiguration}.
+ *
+ * <p>If this OutputConfiguration isn't targeting a physical camera of a logical
+ * multi-camera, this function returns {@code null}.</p>
+ *
+ * @return The physical camera Id associated with this {@link OutputConfiguration}.
+ *
+ * @hide
+ */
+ public @Nullable String getPhysicalCameraId() {
+ return mPhysicalCameraId;
+ }
+
public static final @android.annotation.NonNull Parcelable.Creator<OutputConfiguration> CREATOR =
new Parcelable.Creator<OutputConfiguration>() {
@Override
@@ -1353,6 +1695,9 @@
dest.writeInt(mTimestampBase);
dest.writeInt(mMirrorMode);
dest.writeInt(mReadoutTimestampEnabled ? 1 : 0);
+ dest.writeInt(mConfiguredFormat);
+ dest.writeInt(mConfiguredDataspace);
+ dest.writeLong(mUsage);
}
/**
@@ -1372,22 +1717,24 @@
return true;
} else if (obj instanceof OutputConfiguration) {
final OutputConfiguration other = (OutputConfiguration) obj;
- if (mRotation != other.mRotation ||
- !mConfiguredSize.equals(other.mConfiguredSize) ||
- mConfiguredFormat != other.mConfiguredFormat ||
- mSurfaceGroupId != other.mSurfaceGroupId ||
- mSurfaceType != other.mSurfaceType ||
- mIsDeferredConfig != other.mIsDeferredConfig ||
- mIsShared != other.mIsShared ||
- mConfiguredDataspace != other.mConfiguredDataspace ||
- mConfiguredGenerationId != other.mConfiguredGenerationId ||
- !Objects.equals(mPhysicalCameraId, other.mPhysicalCameraId) ||
- mIsMultiResolution != other.mIsMultiResolution ||
- mStreamUseCase != other.mStreamUseCase ||
- mTimestampBase != other.mTimestampBase ||
- mMirrorMode != other.mMirrorMode ||
- mReadoutTimestampEnabled != other.mReadoutTimestampEnabled)
+ if (mRotation != other.mRotation
+ || !mConfiguredSize.equals(other.mConfiguredSize)
+ || mConfiguredFormat != other.mConfiguredFormat
+ || mSurfaceGroupId != other.mSurfaceGroupId
+ || mSurfaceType != other.mSurfaceType
+ || mIsDeferredConfig != other.mIsDeferredConfig
+ || mIsShared != other.mIsShared
+ || mConfiguredDataspace != other.mConfiguredDataspace
+ || mConfiguredGenerationId != other.mConfiguredGenerationId
+ || !Objects.equals(mPhysicalCameraId, other.mPhysicalCameraId)
+ || mIsMultiResolution != other.mIsMultiResolution
+ || mStreamUseCase != other.mStreamUseCase
+ || mTimestampBase != other.mTimestampBase
+ || mMirrorMode != other.mMirrorMode
+ || mReadoutTimestampEnabled != other.mReadoutTimestampEnabled
+ || mUsage != other.mUsage) {
return false;
+ }
if (mSensorPixelModesUsed.size() != other.mSensorPixelModesUsed.size()) {
return false;
}
@@ -1416,6 +1763,16 @@
}
/**
+ * Get and increase the next MultiResolution group id.
+ *
+ * If the ID reaches -1, skip it.
+ */
+ private static int getAndIncreaseMultiResolutionGroupId() {
+ return sNextMultiResolutionGroupId.getAndUpdate(i ->
+ i + 1 == SURFACE_GROUP_ID_NONE ? i + 2 : i + 1);
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
@@ -1430,7 +1787,8 @@
mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(),
mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode(),
mDynamicRangeProfile, mColorSpace, mStreamUseCase,
- mTimestampBase, mMirrorMode, mReadoutTimestampEnabled ? 1 : 0);
+ mTimestampBase, mMirrorMode, mReadoutTimestampEnabled ? 1 : 0,
+ Long.hashCode(mUsage));
}
return HashCodeHelpers.hashCode(
@@ -1440,14 +1798,14 @@
mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(),
mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode(),
mDynamicRangeProfile, mColorSpace, mStreamUseCase, mTimestampBase,
- mMirrorMode, mReadoutTimestampEnabled ? 1 : 0);
+ mMirrorMode, mReadoutTimestampEnabled ? 1 : 0, Long.hashCode(mUsage));
}
private static final String TAG = "OutputConfiguration";
// A surfaceGroupId counter used for MultiResolutionImageReader. Its value is
// incremented every time {@link createInstancesForMultiResolutionOutput} is called.
- private static int MULTI_RESOLUTION_GROUP_ID_COUNTER = 0;
+ private static AtomicInteger sNextMultiResolutionGroupId = new AtomicInteger(0);
private ArrayList<Surface> mSurfaces;
private final int mRotation;
@@ -1486,4 +1844,6 @@
private boolean mReadoutTimestampEnabled;
// Whether the timestamp base is set to READOUT_SENSOR
private boolean mIsReadoutSensorTimestampBase;
+ // The usage flags. Only set for instances created for ImageReader without specifying surface.
+ private long mUsage;
}
diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java
index 991f545..b0f354f 100644
--- a/core/java/android/hardware/camera2/params/SessionConfiguration.java
+++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java
@@ -20,6 +20,7 @@
import static com.android.internal.util.Preconditions.*;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -28,6 +29,7 @@
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraDevice.CameraDeviceSetup;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.params.InputConfiguration;
@@ -125,6 +127,34 @@
}
/**
+ * Create a new {@link SessionConfiguration} with sessionType and output configurations.
+ *
+ * <p>The SessionConfiguration objects created by this constructor can be used by
+ * {@link CameraDeviceSetup.isSessionConfigurationSupported} and {@link
+ * CameraDeviceSetup.getSessionCharacteristics} to query a camera device's feature
+ * combination support and session specific characteristics. For the SessionConfiguration
+ * object to be used to create a capture session, {@link #setCallback} must be called to
+ * specify the state callback function, and any incomplete OutputConfigurations must be
+ * completed via {@link OutputConfiguration#addSurface} or
+ * {@link OutputConfiguration#setSurfacesForMultiResolutionOutput} as appropriate.</p>
+ *
+ * @param sessionType The session type.
+ * @param outputs A list of output configurations for the capture session.
+ *
+ * @see #SESSION_REGULAR
+ * @see #SESSION_HIGH_SPEED
+ * @see CameraDevice#createCaptureSession(SessionConfiguration)
+ * @see CameraDeviceSetup#isSessionConfigurationSupported
+ * @see CameraDeviceSetup#getSessionCharacteristics
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP)
+ public SessionConfiguration(@SessionMode int sessionType,
+ @NonNull List<OutputConfiguration> outputs) {
+ mSessionType = sessionType;
+ mOutputConfigurations = Collections.unmodifiableList(new ArrayList<>(outputs));
+ }
+
+ /**
* Create a SessionConfiguration from Parcel.
* No support for parcelable 'mStateCallback' and 'mExecutor' yet.
*/
@@ -376,4 +406,23 @@
return null;
}
}
+
+ /**
+ * Set the state callback and executor.
+ *
+ * <p>This function must be called for the SessionConfiguration object created via {@link
+ * #SessionConfiguration(int, List) SessionConfiguration(int, List<OutputConfiguration>)}
+ * before it's used to create a capture session.</p>
+ *
+ * @param executor The executor which should be used to invoke the callback. In general it is
+ * recommended that camera operations are not done on the main (UI) thread.
+ * @param cb A state callback interface implementation.
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP)
+ public void setCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull CameraCaptureSession.StateCallback cb) {
+ mStateCallback = cb;
+ mExecutor = executor;
+ }
}
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index b85d686..b067095 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -1509,6 +1509,8 @@
return HAL_DATASPACE_HEIF;
case ImageFormat.JPEG_R:
return HAL_DATASPACE_JPEG_R;
+ case ImageFormat.YUV_420_888:
+ return HAL_DATASPACE_JFIF;
default:
return HAL_DATASPACE_UNKNOWN;
}
@@ -2025,6 +2027,10 @@
* @hide
*/
public static final int HAL_DATASPACE_JPEG_R = 0x1005;
+ /**
+ * @hide
+ */
+ public static final int HAL_DATASPACE_JFIF = 0x8C20000;
private static final long DURATION_20FPS_NS = 50000000L;
/**
* @see #getDurations(int, int)
diff --git a/core/java/android/hardware/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java
index 76888f3..b214da2 100644
--- a/core/java/android/hardware/devicestate/DeviceState.java
+++ b/core/java/android/hardware/devicestate/DeviceState.java
@@ -52,78 +52,6 @@
@FlaggedApi(android.hardware.devicestate.feature.flags.Flags.FLAG_DEVICE_STATE_PROPERTY_API)
public final class DeviceState {
/**
- * Flag that indicates override requests should be cancelled when this device state becomes the
- * base device state.
- * @hide
- * @deprecated use {@link #PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS}
- */
- @Deprecated
- public static final int FLAG_CANCEL_OVERRIDE_REQUESTS = 1 << 0;
-
- /**
- * Flag that indicates this device state is inaccessible for applications to be placed in. This
- * could be a device-state where the {@link Display#DEFAULT_DISPLAY} is not enabled.
- * @hide
- * @deprecated use {@link #PROPERTY_APP_INACCESSIBLE}
- */
- @Deprecated
- public static final int FLAG_APP_INACCESSIBLE = 1 << 1;
-
- /**
- * Some device states can be both entered through a physical configuration as well as emulation
- * through {@link DeviceStateManager#requestState}, while some states can only be entered
- * through emulation and have no physical configuration to match.
- *
- * This flag indicates that the corresponding state can only be entered through emulation.
- * @hide
- * @deprecated use {@link #PROPERTY_EMULATED_ONLY}
- */
- @Deprecated
- public static final int FLAG_EMULATED_ONLY = 1 << 2;
-
- /**
- * This flag indicates that the corresponding state should be automatically canceled when the
- * requesting app is no longer on top. The app is considered not on top when (1) the top
- * activity in the system is from a different app, (2) the device is in sleep mode, or
- * (3) the keyguard shows up.
- * @hide
- * @deprecated use {@link #PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP}
- */
- @Deprecated
- public static final int FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP = 1 << 3;
-
- /**
- * This flag indicates that the corresponding state should be disabled when the device is
- * overheating and reaching the critical status.
- * @hide
- * @deprecated use {@link #PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL}
- */
- @Deprecated
- public static final int FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL = 1 << 4;
-
- /**
- * This flag indicates that the corresponding state should be disabled when power save mode
- * is enabled.
- * @hide
- * @deprecated use {@link #PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE}
- */
- @Deprecated
- public static final int FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE = 1 << 5;
-
- /** @hide */
- @IntDef(prefix = {"FLAG_"}, flag = true, value = {
- FLAG_CANCEL_OVERRIDE_REQUESTS,
- FLAG_APP_INACCESSIBLE,
- FLAG_EMULATED_ONLY,
- FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP,
- FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL,
- FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE
- })
- @Deprecated
- @Retention(RetentionPolicy.SOURCE)
- public @interface DeviceStateFlags {}
-
- /**
* Property that indicates that a fold-in style foldable device is currently in a fully closed
* configuration.
*/
@@ -302,42 +230,11 @@
@NonNull
private final DeviceState.Configuration mDeviceStateConfiguration;
- @DeviceStateFlags
- private final int mFlags;
-
/** @hide */
+ @TestApi
public DeviceState(@NonNull DeviceState.Configuration deviceStateConfiguration) {
Objects.requireNonNull(deviceStateConfiguration, "Device StateConfiguration is null");
mDeviceStateConfiguration = deviceStateConfiguration;
- mFlags = 0;
- }
-
- /** @hide */
- public DeviceState(
- @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to =
- MAXIMUM_DEVICE_STATE_IDENTIFIER) int identifier,
- @NonNull String name,
- @NonNull Set<@DeviceStateProperties Integer> properties) {
- mDeviceStateConfiguration = new DeviceState.Configuration(identifier, name, properties,
- Collections.emptySet());
- mFlags = 0;
- }
-
- /**
- * @deprecated Deprecated in favor of {@link #DeviceState(int, String, Set)}
- * @hide
- */
- // TODO(b/325124054): Make non-default and remove deprecated callback methods.
- @Deprecated
- public DeviceState(
- @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to =
- MAXIMUM_DEVICE_STATE_IDENTIFIER) int identifier,
- @NonNull String name,
- @DeviceStateFlags int flags) {
-
- mDeviceStateConfiguration = new DeviceState.Configuration(identifier, name,
- Collections.emptySet(), Collections.emptySet());
- mFlags = flags;
}
/** Returns the unique identifier for the device state. */
@@ -352,17 +249,6 @@
return mDeviceStateConfiguration.getName();
}
- /**
- * @hide
- * @deprecated in favor of {@link #hasProperty(int)} method
- */
- // TODO(b/325124054): Make non-default and remove deprecated callback methods.
- @Deprecated
- @DeviceStateFlags
- public int getFlags() {
- return mFlags;
- }
-
@Override
public String toString() {
return "DeviceState{" + "identifier=" + mDeviceStateConfiguration.getIdentifier()
@@ -388,16 +274,6 @@
return Objects.hash(mDeviceStateConfiguration);
}
- /** Checks if a specific flag is set
- * @hide
- * @deprecated in favor of {@link #hasProperty(int)}
- */
- // TODO(b/325124054): Make non-default and remove deprecated callback methods.
- @Deprecated
- public boolean hasFlag(int flagToCheckFor) {
- return (mFlags & flagToCheckFor) == flagToCheckFor;
- }
-
/**
* Checks if a specific property is set on this state
*/
@@ -438,6 +314,7 @@
* @see DeviceStateManager
* @hide
*/
+ @TestApi
public static final class Configuration implements Parcelable {
/** Unique identifier for the device state. */
@IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to = MAXIMUM_DEVICE_STATE_IDENTIFIER)
@@ -563,29 +440,35 @@
};
/** @hide */
- public static class Builder {
+ @TestApi
+ public static final class Builder {
private final int mIdentifier;
+ @NonNull
private final String mName;
+ @NonNull
private Set<@SystemDeviceStateProperties Integer> mSystemProperties =
Collections.emptySet();
+ @NonNull
private Set<@PhysicalDeviceStateProperties Integer> mPhysicalProperties =
Collections.emptySet();
- public Builder(int identifier, String name) {
+ public Builder(int identifier, @NonNull String name) {
mIdentifier = identifier;
mName = name;
}
/** Sets the system properties for this {@link DeviceState.Configuration.Builder} */
+ @NonNull
public Builder setSystemProperties(
- Set<@SystemDeviceStateProperties Integer> systemProperties) {
+ @NonNull Set<@SystemDeviceStateProperties Integer> systemProperties) {
mSystemProperties = systemProperties;
return this;
}
/** Sets the system properties for this {@link DeviceState.Configuration.Builder} */
+ @NonNull
public Builder setPhysicalProperties(
- Set<@PhysicalDeviceStateProperties Integer> physicalProperties) {
+ @NonNull Set<@PhysicalDeviceStateProperties Integer> physicalProperties) {
mPhysicalProperties = physicalProperties;
return this;
}
@@ -594,6 +477,7 @@
* Returns a new {@link DeviceState.Configuration} whose values match the values set on
* the builder.
*/
+ @NonNull
public DeviceState.Configuration build() {
return new DeviceState.Configuration(mIdentifier, mName, mSystemProperties,
mPhysicalProperties);
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index a4c3833..febc24c1 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -49,6 +49,7 @@
*
* @hide
*/
+ @TestApi
public static final int INVALID_DEVICE_STATE_IDENTIFIER = -1;
/**
@@ -96,20 +97,6 @@
/**
* Returns the list of device states that are supported and can be requested with
* {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
- * @deprecated use {@link #getSupportedDeviceStates()}
- * @hide
- */
- // TODO(b/325124054): Make non-default and remove deprecated callback methods.
- @TestApi
- @Deprecated
- @NonNull
- public int[] getSupportedStates() {
- return mGlobal.getSupportedStates();
- }
-
- /**
- * Returns the list of device states that are supported and can be requested with
- * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
*/
@NonNull
public List<DeviceState> getSupportedDeviceStates() {
@@ -239,23 +226,6 @@
* Guaranteed to be called once on registration of the callback with the initial value and
* then on every subsequent change in the supported states.
*
- * @param supportedStates the new supported states.
- *
- * @see DeviceStateManager#getSupportedStates()
- * @deprecated use {@link #onSupportedStatesChanged(List)}
- * @hide
- */
- // TODO(b/325124054): Make non-default and remove deprecated callback methods.
- @TestApi
- @Deprecated
- default void onSupportedStatesChanged(@NonNull int[] supportedStates) {}
-
- /**
- * Called in response to a change in the states supported by the device.
- * <p>
- * Guaranteed to be called once on registration of the callback with the initial value and
- * then on every subsequent change in the supported states.
- *
* The supported device states may change due to certain states becoming unavailable
* due to device configuration or device conditions such as if the device is too hot or
* external monitors have been connected.
@@ -267,42 +237,6 @@
default void onSupportedStatesChanged(@NonNull List<DeviceState> supportedStates) {}
/**
- * Called in response to a change in the base device state.
- * <p>
- * The base state is the state of the device without considering any requests made through
- * calls to {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}
- * from any client process. The base state is guaranteed to match the state provided with a
- * call to {@link #onStateChanged(int)} when there are no active requests from any process.
- * <p>
- * Guaranteed to be called once on registration of the callback with the initial value and
- * then on every subsequent change in the non-override state.
- *
- * @param state the new base device state.
- * @deprecated use {@link #onDeviceStateChanged(DeviceState)} and query for physical
- * properties that are relevant to your needs.
- * @hide
- */
- // TODO(b/325124054): Make non-default and remove deprecated callback methods.
- @TestApi
- @Deprecated
- default void onBaseStateChanged(int state) {}
-
- /**
- * Called in response to device state changes.
- * <p>
- * Guaranteed to be called once on registration of the callback with the initial value and
- * then on every subsequent change in device state.
- *
- * @param state the new device state.
- * @deprecated use {@link #onDeviceStateChanged(DeviceState)}
- * @hide
- */
- // TODO(b/325124054): Make non-default and remove deprecated callback methods.
- @TestApi
- @Deprecated
- void onStateChanged(int state);
-
- /**
* Called in response to device state changes.
* <p>
* Guaranteed to be called once on registration of the callback with the initial value and
@@ -310,8 +244,7 @@
*
* @param state the new device state.
*/
- // TODO(b/325124054): Make non-default and remove deprecated callback methods.
- default void onDeviceStateChanged(@NonNull DeviceState state) {}
+ void onDeviceStateChanged(@NonNull DeviceState state);
}
/**
@@ -340,9 +273,6 @@
}
@Override
- public final void onStateChanged(int state) {}
-
- @Override
public final void onDeviceStateChanged(@NonNull DeviceState deviceState) {
final boolean folded;
if (mFeatureFlags.deviceStatePropertyApi()) {
diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
index d6cc00d..0c84019 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
@@ -90,33 +90,6 @@
}
/**
- * Returns the set of supported device states.
- *
- * @see DeviceStateManager#getSupportedStates()
- */
- // TODO(b/325124054): Remove unused methods when clients are migrated.
- public int[] getSupportedStates() {
- synchronized (mLock) {
- final DeviceStateInfo currentInfo;
- if (mLastReceivedInfo != null) {
- // If we have mLastReceivedInfo a callback is registered for this instance and it
- // is receiving the most recent info from the server. Use that info here.
- currentInfo = mLastReceivedInfo;
- } else {
- // If mLastReceivedInfo is null there is no registered callback so we manually
- // fetch the current info.
- try {
- currentInfo = mDeviceStateManager.getDeviceStateInfo();
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- }
-
- return getSupportedStateIdentifiersLocked(currentInfo.supportedStates);
- }
- }
-
- /**
* Returns {@link List} of supported {@link DeviceState}s.
*
* @see DeviceStateManager#getSupportedDeviceStates()
@@ -264,14 +237,8 @@
mCallbacks.add(wrapper);
if (mLastReceivedInfo != null) {
- // Copy the array to prevent the callback from modifying the internal state.
- final int[] supportedStates = getSupportedStateIdentifiersLocked(
- mLastReceivedInfo.supportedStates);
- wrapper.notifySupportedStatesChanged(supportedStates);
wrapper.notifySupportedDeviceStatesChanged(
List.copyOf(mLastReceivedInfo.supportedStates));
- wrapper.notifyBaseStateChanged(mLastReceivedInfo.baseState.getIdentifier());
- wrapper.notifyStateChanged(mLastReceivedInfo.currentState.getIdentifier());
wrapper.notifyDeviceStateChanged(mLastReceivedInfo.currentState);
}
}
@@ -330,15 +297,6 @@
return -1;
}
- @GuardedBy("mLock")
- private int[] getSupportedStateIdentifiersLocked(List<DeviceState> states) {
- int[] identifiers = new int[states.size()];
- for (int i = 0; i < states.size(); i++) {
- identifiers[i] = states.get(i).getIdentifier();
- }
- return identifiers;
- }
-
@Nullable
private IBinder findRequestTokenLocked(@NonNull DeviceStateRequest request) {
for (int i = 0; i < mRequests.size(); i++) {
@@ -353,12 +311,10 @@
private void handleDeviceStateInfoChanged(@NonNull DeviceStateInfo info) {
ArrayList<DeviceStateCallbackWrapper> callbacks;
DeviceStateInfo oldInfo;
- int[] supportedStateIdentifiers;
synchronized (mLock) {
oldInfo = mLastReceivedInfo;
mLastReceivedInfo = info;
callbacks = new ArrayList<>(mCallbacks);
- supportedStateIdentifiers = getSupportedStateIdentifiersLocked(info.supportedStates);
}
final int diff = oldInfo == null ? ~0 : info.diff(oldInfo);
@@ -366,18 +322,11 @@
for (int i = 0; i < callbacks.size(); i++) {
callbacks.get(i).notifySupportedDeviceStatesChanged(
List.copyOf(info.supportedStates));
- callbacks.get(i).notifySupportedStatesChanged(supportedStateIdentifiers);
- }
- }
- if ((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0) {
- for (int i = 0; i < callbacks.size(); i++) {
- callbacks.get(i).notifyBaseStateChanged(info.baseState.getIdentifier());
}
}
if ((diff & DeviceStateInfo.CHANGED_CURRENT_STATE) > 0) {
for (int i = 0; i < callbacks.size(); i++) {
callbacks.get(i).notifyDeviceStateChanged(info.currentState);
- callbacks.get(i).notifyStateChanged(info.currentState.getIdentifier());
}
}
}
@@ -439,26 +388,11 @@
mExecutor = executor;
}
- void notifySupportedStatesChanged(int[] newSupportedStates) {
- mExecutor.execute(() ->
- mDeviceStateCallback.onSupportedStatesChanged(newSupportedStates));
- }
-
void notifySupportedDeviceStatesChanged(List<DeviceState> newSupportedDeviceStates) {
mExecutor.execute(() ->
mDeviceStateCallback.onSupportedStatesChanged(newSupportedDeviceStates));
}
- void notifyBaseStateChanged(int newBaseState) {
- execute("notifyBaseStateChanged",
- () -> mDeviceStateCallback.onBaseStateChanged(newBaseState));
- }
-
- void notifyStateChanged(int newDeviceState) {
- execute("notifyStateChanged",
- () -> mDeviceStateCallback.onStateChanged(newDeviceState));
- }
-
void notifyDeviceStateChanged(DeviceState newDeviceState) {
execute("notifyDeviceStateChanged",
() -> mDeviceStateCallback.onDeviceStateChanged(newDeviceState));
diff --git a/core/java/android/hardware/devicestate/DeviceStateRequest.java b/core/java/android/hardware/devicestate/DeviceStateRequest.java
index 893d765..7665e2f 100644
--- a/core/java/android/hardware/devicestate/DeviceStateRequest.java
+++ b/core/java/android/hardware/devicestate/DeviceStateRequest.java
@@ -115,8 +115,8 @@
* requested state.
* <p>
* Guaranteed to be called after a call to
- * {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)} with a state
- * matching the requested state.
+ * {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)} with a
+ * state matching the requested state.
*/
default void onRequestActivated(@NonNull DeviceStateRequest request) {}
@@ -124,7 +124,7 @@
* Called to indicate the request has been temporarily suspended.
* <p>
* Guaranteed to be called before a call to
- * {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)}.
+ * {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)}.
*/
default void onRequestSuspended(@NonNull DeviceStateRequest request) {}
@@ -134,7 +134,7 @@
* DeviceStateRequest.Callback)}.
* <p>
* Guaranteed to be called before a call to
- * {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)}.
+ * {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)}.
* <p>
* Note: A call to {@link #onRequestSuspended(DeviceStateRequest)} is not guaranteed to
* occur before this method.
diff --git a/core/java/android/hardware/devicestate/DeviceStateUtil.java b/core/java/android/hardware/devicestate/DeviceStateUtil.java
new file mode 100644
index 0000000..627e740
--- /dev/null
+++ b/core/java/android/hardware/devicestate/DeviceStateUtil.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.devicestate;
+
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
+
+import android.annotation.NonNull;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Utilities for {@link DeviceStateManager}.
+ * @hide
+ */
+public class DeviceStateUtil {
+ private DeviceStateUtil() { }
+
+ /**
+ * Returns the state identifier of the {@link DeviceState} that matches the
+ * {@code currentState}s physical properties. This will return the identifier of the
+ * {@link DeviceState} that matches the devices physical configuration.
+ *
+ * Returns {@link INVALID_DEVICE_STATE_IDENTIFIER} if there is no {@link DeviceState} in the
+ * provided list of {@code supportedStates} that matches.
+ * @hide
+ */
+ public static int calculateBaseStateIdentifier(@NonNull DeviceState currentState,
+ @NonNull List<DeviceState> supportedStates) {
+ DeviceState.Configuration stateConfiguration = currentState.getConfiguration();
+ for (int i = 0; i < supportedStates.size(); i++) {
+ DeviceState stateToCompare = supportedStates.get(i);
+ if (stateToCompare.getConfiguration().getPhysicalProperties().isEmpty()) {
+ continue;
+ }
+ if (isDeviceStateMatchingPhysicalProperties(stateConfiguration.getPhysicalProperties(),
+ supportedStates.get(i))) {
+ return supportedStates.get(i).getIdentifier();
+ }
+ }
+ return INVALID_DEVICE_STATE_IDENTIFIER;
+ }
+
+ /**
+ * Returns if the physical properties provided, matches the same physical properties on the
+ * provided {@link DeviceState}.
+ */
+ private static boolean isDeviceStateMatchingPhysicalProperties(
+ Set<@DeviceState.PhysicalDeviceStateProperties Integer> physicalProperties,
+ DeviceState state) {
+ Iterator<@DeviceState.PhysicalDeviceStateProperties Integer> iterator =
+ physicalProperties.iterator();
+ while (iterator.hasNext()) {
+ if (!state.hasProperty(iterator.next())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index eb26a76..4894fb1 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -1653,6 +1653,22 @@
}
/**
+ * Allows internal application to restrict display modes to specified modeIds
+ *
+ * @param displayId display that restrictions will be applied to
+ * @param modeIds allowed mode ids
+ *
+ * @hide
+ */
+ @RequiresPermission("android.permission.RESTRICT_DISPLAY_MODES")
+ public void requestDisplayModes(int displayId, @Nullable int[] modeIds) {
+ if (modeIds != null && modeIds.length == 0) {
+ throw new IllegalArgumentException("requestDisplayModes: modesIds can't be empty");
+ }
+ mGlobal.requestDisplayModes(displayId, modeIds);
+ }
+
+ /**
* Listens for changes in available display devices.
*/
public interface DisplayListener {
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 75f0ceb..3d7b714 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -38,6 +38,7 @@
import android.hardware.graphics.common.DisplayDecorationSupport;
import android.media.projection.IMediaProjection;
import android.media.projection.MediaProjection;
+import android.os.Binder;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.IBinder;
@@ -138,6 +139,8 @@
private int mWifiDisplayScanNestCount;
+ private final Binder mToken = new Binder();
+
@VisibleForTesting
public DisplayManagerGlobal(IDisplayManager dm) {
mDm = dm;
@@ -1182,6 +1185,20 @@
}
}
+ /**
+ * Sets allowed display mode ids
+ *
+ * @hide
+ */
+ @RequiresPermission("android.permission.RESTRICT_DISPLAY_MODES")
+ public void requestDisplayModes(int displayId, @Nullable int[] modeIds) {
+ try {
+ mDm.requestDisplayModes(mToken, displayId, modeIds);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
@Override
public void onDisplayEvent(int displayId, @DisplayEvent int event) {
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 83de4e4..70efc6f 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -235,4 +235,8 @@
// Disable a connected display that is enabled.
@EnforcePermission("MANAGE_DISPLAYS")
void disableConnectedDisplay(int displayId);
+
+ // Restricts display modes to specified modeIds.
+ @EnforcePermission("RESTRICT_DISPLAY_MODES")
+ void requestDisplayModes(in IBinder token, int displayId, in @nullable int[] modeIds);
}
diff --git a/core/java/android/hardware/location/NanoAppMessage.java b/core/java/android/hardware/location/NanoAppMessage.java
index 48aa1bd..905caf0 100644
--- a/core/java/android/hardware/location/NanoAppMessage.java
+++ b/core/java/android/hardware/location/NanoAppMessage.java
@@ -164,16 +164,18 @@
/**
* Sets the isReliable field of the message
+ *
+ * @hide
*/
- @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
public void setIsReliable(boolean isReliable) {
mIsReliable = isReliable;
}
/**
* Sets the message sequence number of the message
+ *
+ * @hide
*/
- @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
public void setMessageSequenceNumber(int messageSequenceNumber) {
mMessageSequenceNumber = messageSequenceNumber;
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index fdaa0b4..84619a0 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1024,7 +1024,11 @@
/**
* Specifies if a user is disallowed from creating a private profile.
* <p>The default value for an unmanaged user is <code>false</code>.
- * For users with a device owner set, the default is <code>true</code>.
+ * For users with a device owner set, the default value is <code>true</code> and the
+ * device owner currently cannot change it to <code>false</code>.
+ * On organization-owned managed profile devices, the default value is <code>false</code> but
+ * the profile owner can change it to <code>true</code> via the parent profile to block creating
+ * of private profiles on the personal user.
*
* <p>Holders of the permission
* {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PROFILES}
@@ -1034,9 +1038,10 @@
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#getParentProfileInstance(ComponentName)
* @see #getUserRestrictions()
- * @hide
*/
+ @FlaggedApi(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE)
public static final String DISALLOW_ADD_PRIVATE_PROFILE = "no_add_private_profile";
/**
@@ -3189,6 +3194,7 @@
conditional = true)
@UserHandleAware
public boolean canAddPrivateProfile() {
+ if (!android.multiuser.Flags.enablePrivateSpaceFeatures()) return false;
if (android.multiuser.Flags.blockPrivateSpaceCreation()) {
try {
return mService.canAddPrivateProfile(mUserId);
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 943014c..375d729 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -38,6 +38,7 @@
bug: "288119641"
}
+# This flag guards the private space feature, its APIs, and some of the feature implementations. The flag android.multiuser.Flags.enable_private_space_features exclusively guards all the implementations.
flag {
name: "allow_private_profile"
namespace: "profile_experiences"
diff --git a/core/java/android/service/media/OWNERS b/core/java/android/service/media/OWNERS
deleted file mode 100644
index 916fc36..0000000
--- a/core/java/android/service/media/OWNERS
+++ /dev/null
@@ -1,8 +0,0 @@
-# Bug component: 137631
-
-hdmoon@google.com
-insun@google.com
-jaewan@google.com
-jinpark@google.com
-klhyun@google.com
-gyumin@google.com
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
index 73257ed..799c7545 100644
--- a/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
+++ b/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
@@ -20,7 +20,6 @@
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.ITokenInfoCallback;
import android.app.ondeviceintelligence.IProcessingSignal;
-import android.app.ondeviceintelligence.Content;
import android.app.ondeviceintelligence.Feature;
import android.os.ICancellationSignal;
import android.os.PersistableBundle;
@@ -35,12 +34,12 @@
*/
oneway interface IOnDeviceSandboxedInferenceService {
void registerRemoteStorageService(in IRemoteStorageService storageService);
- void requestTokenInfo(int callerUid, in Feature feature, in Content request, in ICancellationSignal cancellationSignal,
+ void requestTokenInfo(int callerUid, in Feature feature, in Bundle request, in ICancellationSignal cancellationSignal,
in ITokenInfoCallback tokenInfoCallback);
- void processRequest(int callerUid, in Feature feature, in Content request, in int requestType,
+ void processRequest(int callerUid, in Feature feature, in Bundle request, in int requestType,
in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal,
in IResponseCallback callback);
- void processRequestStreaming(int callerUid, in Feature feature, in Content request, in int requestType,
+ void processRequestStreaming(int callerUid, in Feature feature, in Bundle request, in int requestType,
in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal,
in IStreamingResponseCallback callback);
void updateProcessingState(in Bundle processingState,
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
index fce3689..6815440 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
@@ -32,7 +32,9 @@
import android.app.ondeviceintelligence.IFeatureCallback;
import android.app.ondeviceintelligence.IFeatureDetailsCallback;
import android.app.ondeviceintelligence.IListFeaturesCallback;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
@@ -48,14 +50,8 @@
import com.android.internal.infra.AndroidFuture;
-import androidx.annotation.IntDef;
-
import java.io.File;
import java.io.FileNotFoundException;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -226,15 +222,18 @@
/**
* Invoked by the {@link OnDeviceIntelligenceService} inorder to send updates to the inference
- * service if there is a state change to be performed.
+ * service if there is a state change to be performed. State change could be config updates,
+ * performing initialization or cleanup tasks in the remote inference service.
+ * The Bundle passed in here is expected to be read-only and will be rejected if it has any
+ * writable fields as detailed under {@link InferenceParams}.
*
* @param processingState the updated state to be applied.
* @param callbackExecutor executor to the run status callback on.
* @param statusReceiver receiver to get status of the update state operation.
*/
- public final void updateProcessingState(@NonNull Bundle processingState,
+ public final void updateProcessingState(@NonNull @InferenceParams Bundle processingState,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull OutcomeReceiver<PersistableBundle, OnDeviceUpdateProcessingException> statusReceiver) {
+ @NonNull OutcomeReceiver<PersistableBundle, OnDeviceIntelligenceException> statusReceiver) {
Objects.requireNonNull(callbackExecutor);
if (mRemoteProcessingService == null) {
throw new IllegalStateException("Remote processing service is unavailable.");
@@ -254,7 +253,7 @@
public void onFailure(int errorCode, String errorMessage) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> statusReceiver.onError(
- new OnDeviceUpdateProcessingException(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage))));
}
});
@@ -265,7 +264,7 @@
}
private OutcomeReceiver<Feature,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapFeatureCallback(
+ OnDeviceIntelligenceException> wrapFeatureCallback(
IFeatureCallback featureCallback) {
return new OutcomeReceiver<>() {
@Override
@@ -279,7 +278,7 @@
@Override
public void onError(
- @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+ @NonNull OnDeviceIntelligenceException exception) {
try {
featureCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -291,7 +290,7 @@
}
private OutcomeReceiver<List<Feature>,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapListFeaturesCallback(
+ OnDeviceIntelligenceException> wrapListFeaturesCallback(
IListFeaturesCallback listFeaturesCallback) {
return new OutcomeReceiver<>() {
@Override
@@ -305,7 +304,7 @@
@Override
public void onError(
- @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+ @NonNull OnDeviceIntelligenceException exception) {
try {
listFeaturesCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -317,7 +316,7 @@
}
private OutcomeReceiver<FeatureDetails,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapFeatureDetailsCallback(
+ OnDeviceIntelligenceException> wrapFeatureDetailsCallback(
IFeatureDetailsCallback featureStatusCallback) {
return new OutcomeReceiver<>() {
@Override
@@ -331,7 +330,7 @@
@Override
public void onError(
- @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+ @NonNull OnDeviceIntelligenceException exception) {
try {
featureStatusCallback.onFailure(exception.getErrorCode(),
exception.getMessage(), exception.getErrorParams());
@@ -444,7 +443,7 @@
*/
public abstract void onGetFeatureDetails(int callerUid, @NonNull Feature feature,
@NonNull OutcomeReceiver<FeatureDetails,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureDetailsCallback);
+ OnDeviceIntelligenceException> featureDetailsCallback);
/**
@@ -455,7 +454,7 @@
*/
public abstract void onGetFeature(int callerUid, int featureId,
@NonNull OutcomeReceiver<Feature,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureCallback);
+ OnDeviceIntelligenceException> featureCallback);
/**
* List all features which are available in the remote implementation. The implementation might
@@ -465,7 +464,7 @@
* @param listFeaturesCallback callback to populate the features list.
*/
public abstract void onListFeatures(int callerUid, @NonNull OutcomeReceiver<List<Feature>,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> listFeaturesCallback);
+ OnDeviceIntelligenceException> listFeaturesCallback);
/**
* Provides a long value representing the version of the remote implementation processing
@@ -474,60 +473,4 @@
* @param versionConsumer consumer to populate the version.
*/
public abstract void onGetVersion(@NonNull LongConsumer versionConsumer);
-
-
- /**
- * Exception type to be populated when calls to {@link #updateProcessingState} fail.
- */
- public static class OnDeviceUpdateProcessingException extends
- OnDeviceIntelligenceServiceException {
- /**
- * The connection to remote service failed and the processing state could not be updated.
- */
- public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 1;
-
-
- /**
- * @hide
- */
- @IntDef(value = {
- PROCESSING_UPDATE_STATUS_CONNECTION_FAILED
- }, open = true)
- @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER,
- ElementType.FIELD})
- @Retention(RetentionPolicy.SOURCE)
- public @interface ErrorCode {
- }
-
- public OnDeviceUpdateProcessingException(@ErrorCode int errorCode) {
- super(errorCode);
- }
-
- public OnDeviceUpdateProcessingException(@ErrorCode int errorCode,
- @NonNull String errorMessage) {
- super(errorCode, errorMessage);
- }
- }
-
- /**
- * Exception type to be used for surfacing errors to service implementation.
- */
- public abstract static class OnDeviceIntelligenceServiceException extends Exception {
- private final int mErrorCode;
-
- public OnDeviceIntelligenceServiceException(int errorCode) {
- this.mErrorCode = errorCode;
- }
-
- public OnDeviceIntelligenceServiceException(int errorCode,
- @NonNull String errorMessage) {
- super(errorMessage);
- this.mErrorCode = errorCode;
- }
-
- public int getErrorCode() {
- return mErrorCode;
- }
-
- }
}
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
index 7f7f9c2..d943c80 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
@@ -27,20 +27,21 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.Service;
-import android.app.ondeviceintelligence.Content;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
+import android.os.Bundle;
import android.app.ondeviceintelligence.Feature;
import android.app.ondeviceintelligence.IProcessingSignal;
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.IStreamingResponseCallback;
import android.app.ondeviceintelligence.ITokenInfoCallback;
import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams;
import android.app.ondeviceintelligence.ProcessingSignal;
-import android.app.ondeviceintelligence.ProcessingOutcomeReceiver;
-import android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver;
+import android.app.ondeviceintelligence.ProcessingCallback;
+import android.app.ondeviceintelligence.StreamingProcessingCallback;
import android.app.ondeviceintelligence.TokenInfo;
import android.content.Context;
import android.content.Intent;
-import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.HandlerExecutor;
@@ -51,7 +52,6 @@
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.RemoteException;
-import android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException;
import android.util.Log;
import android.util.Slog;
@@ -121,7 +121,7 @@
}
@Override
- public void requestTokenInfo(int callerUid, Feature feature, Content request,
+ public void requestTokenInfo(int callerUid, Feature feature, Bundle request,
ICancellationSignal cancellationSignal,
ITokenInfoCallback tokenInfoCallback) {
Objects.requireNonNull(feature);
@@ -134,7 +134,7 @@
}
@Override
- public void processRequestStreaming(int callerUid, Feature feature, Content request,
+ public void processRequestStreaming(int callerUid, Feature feature, Bundle request,
int requestType, ICancellationSignal cancellationSignal,
IProcessingSignal processingSignal,
IStreamingResponseCallback callback) {
@@ -151,7 +151,7 @@
}
@Override
- public void processRequest(int callerUid, Feature feature, Content request,
+ public void processRequest(int callerUid, Feature feature, Bundle request,
int requestType, ICancellationSignal cancellationSignal,
IProcessingSignal processingSignal,
IResponseCallback callback) {
@@ -198,18 +198,17 @@
@NonNull
public abstract void onTokenInfoRequest(
int callerUid, @NonNull Feature feature,
- @NonNull Content request,
+ @NonNull @InferenceParams Bundle request,
@Nullable CancellationSignal cancellationSignal,
- @NonNull OutcomeReceiver<TokenInfo,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
+ @NonNull OutcomeReceiver<TokenInfo, OnDeviceIntelligenceException> callback);
/**
* Invoked when caller provides a request for a particular feature to be processed in a
* streaming manner. The expectation from the implementation is that when processing the
* request,
- * it periodically populates the {@link StreamedProcessingOutcomeReceiver#onNewContent} to continuously
- * provide partial Content results for the caller to utilize. Optionally the implementation can
- * provide the complete response in the {@link StreamedProcessingOutcomeReceiver#onResult} upon
+ * it periodically populates the {@link StreamingProcessingCallback#onPartialResult} to continuously
+ * provide partial Bundle results for the caller to utilize. Optionally the implementation can
+ * provide the complete response in the {@link StreamingProcessingCallback#onResult} upon
* processing completion.
*
* @param callerUid UID of the caller that initiated this call chain.
@@ -225,11 +224,11 @@
@NonNull
public abstract void onProcessRequestStreaming(
int callerUid, @NonNull Feature feature,
- @Nullable Content request,
+ @NonNull @InferenceParams Bundle request,
@OnDeviceIntelligenceManager.RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
- @NonNull StreamedProcessingOutcomeReceiver callback);
+ @NonNull StreamingProcessingCallback callback);
/**
* Invoked when caller provides a request for a particular feature to be processed in one shot
@@ -251,11 +250,11 @@
@NonNull
public abstract void onProcessRequest(
int callerUid, @NonNull Feature feature,
- @Nullable Content request,
+ @NonNull @InferenceParams Bundle request,
@OnDeviceIntelligenceManager.RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
- @NonNull ProcessingOutcomeReceiver callback);
+ @NonNull ProcessingCallback callback);
/**
@@ -267,9 +266,9 @@
* @param callback callback to populate the update status and if there are params
* associated with the status.
*/
- public abstract void onUpdateProcessingState(@NonNull Bundle processingState,
+ public abstract void onUpdateProcessingState(@NonNull @InferenceParams Bundle processingState,
@NonNull OutcomeReceiver<PersistableBundle,
- OnDeviceUpdateProcessingException> callback);
+ OnDeviceIntelligenceException> callback);
/**
@@ -344,7 +343,7 @@
/**
* Returns the {@link Executor} to use for incoming IPC from request sender into your service
* implementation. For e.g. see
- * {@link ProcessingOutcomeReceiver#onDataAugmentRequest(Content,
+ * {@link ProcessingCallback#onDataAugmentRequest(Bundle,
* Consumer)} where we use the executor to populate the consumer.
* <p>
* Override this method in your {@link OnDeviceSandboxedInferenceService} implementation to
@@ -380,13 +379,13 @@
});
}
- private ProcessingOutcomeReceiver wrapResponseCallback(
+ private ProcessingCallback wrapResponseCallback(
IResponseCallback callback) {
- return new ProcessingOutcomeReceiver() {
+ return new ProcessingCallback() {
@Override
- public void onResult(@androidx.annotation.NonNull Content response) {
+ public void onResult(@androidx.annotation.NonNull Bundle result) {
try {
- callback.onSuccess(response);
+ callback.onSuccess(result);
} catch (RemoteException e) {
Slog.e(TAG, "Error sending result: " + e);
}
@@ -394,7 +393,7 @@
@Override
public void onError(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
+ OnDeviceIntelligenceException exception) {
try {
callback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -404,8 +403,8 @@
}
@Override
- public void onDataAugmentRequest(@NonNull Content content,
- @NonNull Consumer<Content> contentCallback) {
+ public void onDataAugmentRequest(@NonNull Bundle content,
+ @NonNull Consumer<Bundle> contentCallback) {
try {
callback.onDataAugmentRequest(content, wrapRemoteCallback(contentCallback));
@@ -416,22 +415,22 @@
};
}
- private StreamedProcessingOutcomeReceiver wrapStreamingResponseCallback(
+ private StreamingProcessingCallback wrapStreamingResponseCallback(
IStreamingResponseCallback callback) {
- return new StreamedProcessingOutcomeReceiver() {
+ return new StreamingProcessingCallback() {
@Override
- public void onNewContent(@androidx.annotation.NonNull Content content) {
+ public void onPartialResult(@androidx.annotation.NonNull Bundle partialResult) {
try {
- callback.onNewContent(content);
+ callback.onNewContent(partialResult);
} catch (RemoteException e) {
Slog.e(TAG, "Error sending result: " + e);
}
}
@Override
- public void onResult(@androidx.annotation.NonNull Content response) {
+ public void onResult(@androidx.annotation.NonNull Bundle result) {
try {
- callback.onSuccess(response);
+ callback.onSuccess(result);
} catch (RemoteException e) {
Slog.e(TAG, "Error sending result: " + e);
}
@@ -439,7 +438,7 @@
@Override
public void onError(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
+ OnDeviceIntelligenceException exception) {
try {
callback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -449,8 +448,8 @@
}
@Override
- public void onDataAugmentRequest(@NonNull Content content,
- @NonNull Consumer<Content> contentCallback) {
+ public void onDataAugmentRequest(@NonNull Bundle content,
+ @NonNull Consumer<Bundle> contentCallback) {
try {
callback.onDataAugmentRequest(content, wrapRemoteCallback(contentCallback));
@@ -462,13 +461,13 @@
}
private RemoteCallback wrapRemoteCallback(
- @androidx.annotation.NonNull Consumer<Content> contentCallback) {
+ @androidx.annotation.NonNull Consumer<Bundle> contentCallback) {
return new RemoteCallback(
result -> {
if (result != null) {
getCallbackExecutor().execute(() -> contentCallback.accept(
result.getParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY,
- Content.class)));
+ Bundle.class)));
} else {
getCallbackExecutor().execute(
() -> contentCallback.accept(null));
@@ -476,8 +475,7 @@
});
}
- private OutcomeReceiver<TokenInfo,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapTokenInfoCallback(
+ private OutcomeReceiver<TokenInfo, OnDeviceIntelligenceException> wrapTokenInfoCallback(
ITokenInfoCallback tokenInfoCallback) {
return new OutcomeReceiver<>() {
@Override
@@ -491,7 +489,7 @@
@Override
public void onError(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
+ OnDeviceIntelligenceException exception) {
try {
tokenInfoCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -503,7 +501,7 @@
}
@NonNull
- private static OutcomeReceiver<PersistableBundle, OnDeviceUpdateProcessingException> wrapOutcomeReceiver(
+ private static OutcomeReceiver<PersistableBundle, OnDeviceIntelligenceException> wrapOutcomeReceiver(
IProcessingUpdateStatusCallback callback) {
return new OutcomeReceiver<>() {
@Override
@@ -518,7 +516,7 @@
@Override
public void onError(
- @androidx.annotation.NonNull OnDeviceUpdateProcessingException error) {
+ @androidx.annotation.NonNull OnDeviceIntelligenceException error) {
try {
callback.onFailure(error.getErrorCode(), error.getMessage());
} catch (RemoteException e) {
diff --git a/core/java/android/service/persistentdata/PersistentDataBlockManager.java b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
index 9b9cc19..7a1c75a 100644
--- a/core/java/android/service/persistentdata/PersistentDataBlockManager.java
+++ b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
@@ -19,6 +19,7 @@
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
@@ -100,7 +101,7 @@
*/
@SystemApi
@SuppressLint("RequiresPermission")
- public int write(byte[] data) {
+ public int write(@Nullable byte[] data) {
try {
return sService.write(data);
} catch (RemoteException e) {
@@ -115,7 +116,7 @@
*/
@SystemApi
@SuppressLint("RequiresPermission")
- public byte[] read() {
+ public @Nullable byte[] read() {
try {
return sService.read();
} catch (RemoteException e) {
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 9db8aa1..e95b216 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -28,6 +28,7 @@
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.text.LineBreakConfig;
+import android.os.Trace;
import android.text.style.ParagraphStyle;
/**
@@ -567,49 +568,59 @@
public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint,
@NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing,
@Nullable Paint.FontMetrics minimumFontMetrics, @Nullable Metrics metrics) {
- final int textLength = text.length();
- if (hasAnyInterestingChars(text, textLength)) {
- return null; // There are some interesting characters. Not boring.
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("BoringLayout#isBoring");
+ Trace.setCounter("BoringLayout#textLength", text.length());
}
- if (textDir != null && textDir.isRtl(text, 0, textLength)) {
- return null; // The heuristic considers the whole text RTL. Not boring.
- }
- if (text instanceof Spanned) {
- Spanned sp = (Spanned) text;
- Object[] styles = sp.getSpans(0, textLength, ParagraphStyle.class);
- if (styles.length > 0) {
- return null; // There are some ParagraphStyle spans. Not boring.
+ try {
+ final int textLength = text.length();
+ if (hasAnyInterestingChars(text, textLength)) {
+ return null; // There are some interesting characters. Not boring.
+ }
+ if (textDir != null && textDir.isRtl(text, 0, textLength)) {
+ return null; // The heuristic considers the whole text RTL. Not boring.
+ }
+ if (text instanceof Spanned) {
+ Spanned sp = (Spanned) text;
+ Object[] styles = sp.getSpans(0, textLength, ParagraphStyle.class);
+ if (styles.length > 0) {
+ return null; // There are some ParagraphStyle spans. Not boring.
+ }
+ }
+
+ Metrics fm = metrics;
+ if (fm == null) {
+ fm = new Metrics();
+ } else {
+ fm.reset();
+ }
+
+ if (ClientFlags.fixLineHeightForLocale()) {
+ if (minimumFontMetrics != null) {
+ fm.set(minimumFontMetrics);
+ // Because the font metrics is provided by public APIs, adjust the top/bottom
+ // with ascent/descent: top must be smaller than ascent, bottom must be larger
+ // than descent.
+ fm.top = Math.min(fm.top, fm.ascent);
+ fm.bottom = Math.max(fm.bottom, fm.descent);
+ }
+ }
+
+ TextLine line = TextLine.obtain();
+ line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
+ Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null,
+ 0 /* ellipsisStart, 0 since text has not been ellipsized at this point */,
+ 0 /* ellipsisEnd, 0 since text has not been ellipsized at this point */,
+ useFallbackLineSpacing);
+ fm.width = (int) Math.ceil(line.metrics(fm, fm.mDrawingBounds, false, null));
+ TextLine.recycle(line);
+
+ return fm;
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
}
}
-
- Metrics fm = metrics;
- if (fm == null) {
- fm = new Metrics();
- } else {
- fm.reset();
- }
-
- if (ClientFlags.fixLineHeightForLocale()) {
- if (minimumFontMetrics != null) {
- fm.set(minimumFontMetrics);
- // Because the font metrics is provided by public APIs, adjust the top/bottom with
- // ascent/descent: top must be smaller than ascent, bottom must be larger than
- // descent.
- fm.top = Math.min(fm.top, fm.ascent);
- fm.bottom = Math.max(fm.bottom, fm.descent);
- }
- }
-
- TextLine line = TextLine.obtain();
- line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
- Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null,
- 0 /* ellipsisStart, 0 since text has not been ellipsized at this point */,
- 0 /* ellipsisEnd, 0 since text has not been ellipsized at this point */,
- useFallbackLineSpacing);
- fm.width = (int) Math.ceil(line.metrics(fm, fm.mDrawingBounds, false, null));
- TextLine.recycle(line);
-
- return fm;
}
@Override
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index cce4f7b..99ce0ef 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -31,6 +31,7 @@
import android.graphics.Rect;
import android.graphics.text.LineBreakConfig;
import android.os.Build;
+import android.os.Trace;
import android.text.method.OffsetMapping;
import android.text.style.ReplacementSpan;
import android.text.style.UpdateLayout;
@@ -636,207 +637,224 @@
/** @hide */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public void reflow(CharSequence s, int where, int before, int after) {
- if (s != mBase)
- return;
-
- CharSequence text = mDisplay;
- int len = text.length();
-
- // seek back to the start of the paragraph
-
- int find = TextUtils.lastIndexOf(text, '\n', where - 1);
- if (find < 0)
- find = 0;
- else
- find = find + 1;
-
- {
- int diff = where - find;
- before += diff;
- after += diff;
- where -= diff;
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("DynamicLayout#reflow");
}
-
- // seek forward to the end of the paragraph
-
- int look = TextUtils.indexOf(text, '\n', where + after);
- if (look < 0)
- look = len;
- else
- look++; // we want the index after the \n
-
- int change = look - (where + after);
- before += change;
- after += change;
-
- // seek further out to cover anything that is forced to wrap together
-
- if (text instanceof Spanned) {
- Spanned sp = (Spanned) text;
- boolean again;
-
- do {
- again = false;
-
- Object[] force = sp.getSpans(where, where + after,
- WrapTogetherSpan.class);
-
- for (int i = 0; i < force.length; i++) {
- int st = sp.getSpanStart(force[i]);
- int en = sp.getSpanEnd(force[i]);
-
- if (st < where) {
- again = true;
-
- int diff = where - st;
- before += diff;
- after += diff;
- where -= diff;
- }
-
- if (en > where + after) {
- again = true;
-
- int diff = en - (where + after);
- before += diff;
- after += diff;
- }
- }
- } while (again);
- }
-
- // find affected region of old layout
-
- int startline = getLineForOffset(where);
- int startv = getLineTop(startline);
-
- int endline = getLineForOffset(where + before);
- if (where + after == len)
- endline = getLineCount();
- int endv = getLineTop(endline);
- boolean islast = (endline == getLineCount());
-
- // generate new layout for affected text
-
- StaticLayout reflowed;
- StaticLayout.Builder b;
-
- synchronized (sLock) {
- reflowed = sStaticLayout;
- b = sBuilder;
- sStaticLayout = null;
- sBuilder = null;
- }
-
- if (b == null) {
- b = StaticLayout.Builder.obtain(text, where, where + after, getPaint(), getWidth());
- }
-
- b.setText(text, where, where + after)
- .setPaint(getPaint())
- .setWidth(getWidth())
- .setTextDirection(getTextDirectionHeuristic())
- .setLineSpacing(getSpacingAdd(), getSpacingMultiplier())
- .setUseLineSpacingFromFallbacks(mFallbackLineSpacing)
- .setEllipsizedWidth(mEllipsizedWidth)
- .setEllipsize(mEllipsizeAt)
- .setBreakStrategy(mBreakStrategy)
- .setHyphenationFrequency(mHyphenationFrequency)
- .setJustificationMode(mJustificationMode)
- .setLineBreakConfig(mLineBreakConfig)
- .setAddLastLineLineSpacing(!islast)
- .setIncludePad(false)
- .setUseBoundsForWidth(mUseBoundsForWidth)
- .setShiftDrawingOffsetForStartOverhang(mShiftDrawingOffsetForStartOverhang)
- .setMinimumFontMetrics(mMinimumFontMetrics)
- .setCalculateBounds(true);
-
- reflowed = b.buildPartialStaticLayoutForDynamicLayout(true /* trackpadding */, reflowed);
- int n = reflowed.getLineCount();
- // If the new layout has a blank line at the end, but it is not
- // the very end of the buffer, then we already have a line that
- // starts there, so disregard the blank line.
-
- if (where + after != len && reflowed.getLineStart(n - 1) == where + after)
- n--;
-
- // remove affected lines from old layout
- mInts.deleteAt(startline, endline - startline);
- mObjects.deleteAt(startline, endline - startline);
-
- // adjust offsets in layout for new height and offsets
-
- int ht = reflowed.getLineTop(n);
- int toppad = 0, botpad = 0;
-
- if (mIncludePad && startline == 0) {
- toppad = reflowed.getTopPadding();
- mTopPadding = toppad;
- ht -= toppad;
- }
- if (mIncludePad && islast) {
- botpad = reflowed.getBottomPadding();
- mBottomPadding = botpad;
- ht += botpad;
- }
-
- mInts.adjustValuesBelow(startline, START, after - before);
- mInts.adjustValuesBelow(startline, TOP, startv - endv + ht);
-
- // insert new layout
-
- int[] ints;
-
- if (mEllipsize) {
- ints = new int[COLUMNS_ELLIPSIZE];
- ints[ELLIPSIS_START] = ELLIPSIS_UNDEFINED;
- } else {
- ints = new int[COLUMNS_NORMAL];
- }
-
- Directions[] objects = new Directions[1];
-
- for (int i = 0; i < n; i++) {
- final int start = reflowed.getLineStart(i);
- ints[START] = start;
- ints[DIR] |= reflowed.getParagraphDirection(i) << DIR_SHIFT;
- ints[TAB] |= reflowed.getLineContainsTab(i) ? TAB_MASK : 0;
-
- int top = reflowed.getLineTop(i) + startv;
- if (i > 0)
- top -= toppad;
- ints[TOP] = top;
-
- int desc = reflowed.getLineDescent(i);
- if (i == n - 1)
- desc += botpad;
-
- ints[DESCENT] = desc;
- ints[EXTRA] = reflowed.getLineExtra(i);
- objects[0] = reflowed.getLineDirections(i);
-
- final int end = (i == n - 1) ? where + after : reflowed.getLineStart(i + 1);
- ints[HYPHEN] = StaticLayout.packHyphenEdit(
- reflowed.getStartHyphenEdit(i), reflowed.getEndHyphenEdit(i));
- ints[MAY_PROTRUDE_FROM_TOP_OR_BOTTOM] |=
- contentMayProtrudeFromLineTopOrBottom(text, start, end) ?
- MAY_PROTRUDE_FROM_TOP_OR_BOTTOM_MASK : 0;
-
- if (mEllipsize) {
- ints[ELLIPSIS_START] = reflowed.getEllipsisStart(i);
- ints[ELLIPSIS_COUNT] = reflowed.getEllipsisCount(i);
+ try {
+ if (s != mBase) {
+ return;
}
- mInts.insertAt(startline + i, ints);
- mObjects.insertAt(startline + i, objects);
- }
+ CharSequence text = mDisplay;
+ int len = text.length();
- updateBlocks(startline, endline - 1, n);
+ // seek back to the start of the paragraph
- b.finish();
- synchronized (sLock) {
- sStaticLayout = reflowed;
- sBuilder = b;
+ int find = TextUtils.lastIndexOf(text, '\n', where - 1);
+ if (find < 0) {
+ find = 0;
+ } else {
+ find = find + 1;
+ }
+
+ {
+ int diff = where - find;
+ before += diff;
+ after += diff;
+ where -= diff;
+ }
+
+ // seek forward to the end of the paragraph
+
+ int look = TextUtils.indexOf(text, '\n', where + after);
+ if (look < 0) {
+ look = len;
+ } else {
+ look++; // we want the index after the \n
+ }
+
+ int change = look - (where + after);
+ before += change;
+ after += change;
+
+ // seek further out to cover anything that is forced to wrap together
+
+ if (text instanceof Spanned) {
+ Spanned sp = (Spanned) text;
+ boolean again;
+
+ do {
+ again = false;
+
+ Object[] force = sp.getSpans(where, where + after,
+ WrapTogetherSpan.class);
+
+ for (int i = 0; i < force.length; i++) {
+ int st = sp.getSpanStart(force[i]);
+ int en = sp.getSpanEnd(force[i]);
+
+ if (st < where) {
+ again = true;
+
+ int diff = where - st;
+ before += diff;
+ after += diff;
+ where -= diff;
+ }
+
+ if (en > where + after) {
+ again = true;
+
+ int diff = en - (where + after);
+ before += diff;
+ after += diff;
+ }
+ }
+ } while (again);
+ }
+
+ // find affected region of old layout
+
+ int startline = getLineForOffset(where);
+ int startv = getLineTop(startline);
+
+ int endline = getLineForOffset(where + before);
+ if (where + after == len) {
+ endline = getLineCount();
+ }
+ int endv = getLineTop(endline);
+ boolean islast = (endline == getLineCount());
+
+ // generate new layout for affected text
+
+ StaticLayout reflowed;
+ StaticLayout.Builder b;
+
+ synchronized (sLock) {
+ reflowed = sStaticLayout;
+ b = sBuilder;
+ sStaticLayout = null;
+ sBuilder = null;
+ }
+
+ if (b == null) {
+ b = StaticLayout.Builder.obtain(text, where, where + after, getPaint(), getWidth());
+ }
+
+ b.setText(text, where, where + after)
+ .setPaint(getPaint())
+ .setWidth(getWidth())
+ .setTextDirection(getTextDirectionHeuristic())
+ .setLineSpacing(getSpacingAdd(), getSpacingMultiplier())
+ .setUseLineSpacingFromFallbacks(mFallbackLineSpacing)
+ .setEllipsizedWidth(mEllipsizedWidth)
+ .setEllipsize(mEllipsizeAt)
+ .setBreakStrategy(mBreakStrategy)
+ .setHyphenationFrequency(mHyphenationFrequency)
+ .setJustificationMode(mJustificationMode)
+ .setLineBreakConfig(mLineBreakConfig)
+ .setAddLastLineLineSpacing(!islast)
+ .setIncludePad(false)
+ .setUseBoundsForWidth(mUseBoundsForWidth)
+ .setShiftDrawingOffsetForStartOverhang(mShiftDrawingOffsetForStartOverhang)
+ .setMinimumFontMetrics(mMinimumFontMetrics)
+ .setCalculateBounds(true);
+
+ reflowed = b.buildPartialStaticLayoutForDynamicLayout(true /* trackpadding */,
+ reflowed);
+ int n = reflowed.getLineCount();
+ // If the new layout has a blank line at the end, but it is not
+ // the very end of the buffer, then we already have a line that
+ // starts there, so disregard the blank line.
+
+ if (where + after != len && reflowed.getLineStart(n - 1) == where + after) {
+ n--;
+ }
+
+ // remove affected lines from old layout
+ mInts.deleteAt(startline, endline - startline);
+ mObjects.deleteAt(startline, endline - startline);
+
+ // adjust offsets in layout for new height and offsets
+
+ int ht = reflowed.getLineTop(n);
+ int toppad = 0, botpad = 0;
+
+ if (mIncludePad && startline == 0) {
+ toppad = reflowed.getTopPadding();
+ mTopPadding = toppad;
+ ht -= toppad;
+ }
+ if (mIncludePad && islast) {
+ botpad = reflowed.getBottomPadding();
+ mBottomPadding = botpad;
+ ht += botpad;
+ }
+
+ mInts.adjustValuesBelow(startline, START, after - before);
+ mInts.adjustValuesBelow(startline, TOP, startv - endv + ht);
+
+ // insert new layout
+
+ int[] ints;
+
+ if (mEllipsize) {
+ ints = new int[COLUMNS_ELLIPSIZE];
+ ints[ELLIPSIS_START] = ELLIPSIS_UNDEFINED;
+ } else {
+ ints = new int[COLUMNS_NORMAL];
+ }
+
+ Directions[] objects = new Directions[1];
+
+ for (int i = 0; i < n; i++) {
+ final int start = reflowed.getLineStart(i);
+ ints[START] = start;
+ ints[DIR] |= reflowed.getParagraphDirection(i) << DIR_SHIFT;
+ ints[TAB] |= reflowed.getLineContainsTab(i) ? TAB_MASK : 0;
+
+ int top = reflowed.getLineTop(i) + startv;
+ if (i > 0) {
+ top -= toppad;
+ }
+ ints[TOP] = top;
+
+ int desc = reflowed.getLineDescent(i);
+ if (i == n - 1) {
+ desc += botpad;
+ }
+
+ ints[DESCENT] = desc;
+ ints[EXTRA] = reflowed.getLineExtra(i);
+ objects[0] = reflowed.getLineDirections(i);
+
+ final int end = (i == n - 1) ? where + after : reflowed.getLineStart(i + 1);
+ ints[HYPHEN] = StaticLayout.packHyphenEdit(
+ reflowed.getStartHyphenEdit(i), reflowed.getEndHyphenEdit(i));
+ ints[MAY_PROTRUDE_FROM_TOP_OR_BOTTOM] |=
+ contentMayProtrudeFromLineTopOrBottom(text, start, end)
+ ? MAY_PROTRUDE_FROM_TOP_OR_BOTTOM_MASK : 0;
+
+ if (mEllipsize) {
+ ints[ELLIPSIS_START] = reflowed.getEllipsisStart(i);
+ ints[ELLIPSIS_COUNT] = reflowed.getEllipsisCount(i);
+ }
+
+ mInts.insertAt(startline + i, ints);
+ mObjects.insertAt(startline + i, objects);
+ }
+
+ updateBlocks(startline, endline - 1, n);
+
+ b.finish();
+ synchronized (sLock) {
+ sStaticLayout = reflowed;
+ sBuilder = b;
+ }
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
+ }
}
}
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 8dee4b1..ce238a7 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -39,6 +39,7 @@
import android.graphics.text.LineBreakConfig;
import android.graphics.text.LineBreaker;
import android.os.Build;
+import android.os.Trace;
import android.text.method.TextKeyListener;
import android.text.style.AlignmentSpan;
import android.text.style.LeadingMarginSpan;
@@ -70,6 +71,11 @@
* For text that will not change, use a {@link StaticLayout}.
*/
public abstract class Layout {
+
+ /** @hide */
+ protected static final boolean TRACE_LAYOUT = Build.isDebuggable();
+
+
/** @hide */
@IntDef(prefix = { "BREAK_STRATEGY_" }, value = {
LineBreaker.BREAK_STRATEGY_SIMPLE,
@@ -472,40 +478,51 @@
@Nullable Path selectionPath,
@Nullable Paint selectionPaint,
int cursorOffsetVertical) {
- float leftShift = 0;
- if (mUseBoundsForWidth && mShiftDrawingOffsetForStartOverhang) {
- RectF drawingRect = computeDrawingBoundingBox();
- if (drawingRect.left < 0) {
- leftShift = -drawingRect.left;
- canvas.translate(leftShift, 0);
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("Layout#draw");
+ }
+ try {
+ float leftShift = 0;
+ if (mUseBoundsForWidth && mShiftDrawingOffsetForStartOverhang) {
+ RectF drawingRect = computeDrawingBoundingBox();
+ if (drawingRect.left < 0) {
+ leftShift = -drawingRect.left;
+ canvas.translate(leftShift, 0);
+ }
}
- }
- final long lineRange = getLineRangeForDraw(canvas);
- int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
- int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
- if (lastLine < 0) return;
+ final long lineRange = getLineRangeForDraw(canvas);
+ int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
+ int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
+ if (lastLine < 0) return;
- if (shouldDrawHighlightsOnTop(canvas)) {
- drawBackground(canvas, firstLine, lastLine);
- } else {
- drawWithoutText(canvas, highlightPaths, highlightPaints, selectionPath, selectionPaint,
- cursorOffsetVertical, firstLine, lastLine);
- }
+ if (shouldDrawHighlightsOnTop(canvas)) {
+ drawBackground(canvas, firstLine, lastLine);
+ } else {
+ drawWithoutText(canvas, highlightPaths, highlightPaints, selectionPath,
+ selectionPaint,
+ cursorOffsetVertical, firstLine, lastLine);
+ }
- drawText(canvas, firstLine, lastLine);
+ drawText(canvas, firstLine, lastLine);
- // Since high contrast text draws a solid rectangle background behind the text, it covers up
- // the highlights and selections. In this case we draw over the top of the text with a
- // blend mode that ensures the text stays high-contrast.
- if (shouldDrawHighlightsOnTop(canvas)) {
- drawHighlights(canvas, highlightPaths, highlightPaints, selectionPath, selectionPaint,
- cursorOffsetVertical, firstLine, lastLine);
- }
+ // Since high contrast text draws a solid rectangle background behind the text, it
+ // covers up the highlights and selections. In this case we draw over the top of the
+ // text with a blend mode that ensures the text stays high-contrast.
+ if (shouldDrawHighlightsOnTop(canvas)) {
+ drawHighlights(canvas, highlightPaths, highlightPaints, selectionPath,
+ selectionPaint,
+ cursorOffsetVertical, firstLine, lastLine);
+ }
- if (leftShift != 0) {
- // Manually translate back to the original position because of b/324498002, using
- // save/restore disappears the toggle switch drawables.
- canvas.translate(-leftShift, 0);
+ if (leftShift != 0) {
+ // Manually translate back to the original position because of b/324498002, using
+ // save/restore disappears the toggle switch drawables.
+ canvas.translate(-leftShift, 0);
+ }
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
+ }
}
}
diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java
index 5f6a9bd..14401a6 100644
--- a/core/java/android/text/PrecomputedText.java
+++ b/core/java/android/text/PrecomputedText.java
@@ -25,6 +25,8 @@
import android.graphics.Rect;
import android.graphics.text.LineBreakConfig;
import android.graphics.text.MeasuredText;
+import android.os.Build;
+import android.os.Trace;
import android.text.style.MetricAffectingSpan;
import com.android.internal.util.Preconditions;
@@ -78,6 +80,8 @@
public class PrecomputedText implements Spannable {
private static final char LINE_FEED = '\n';
+ private static final boolean TRACE_PCT = Build.isDebuggable();
+
/**
* The information required for building {@link PrecomputedText}.
*
@@ -447,35 +451,47 @@
private static ParagraphInfo[] createMeasuredParagraphsFromPrecomputedText(
@NonNull PrecomputedText pct, @NonNull Params params, boolean computeLayout) {
- final boolean needHyphenation = params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
- && params.getHyphenationFrequency() != Layout.HYPHENATION_FREQUENCY_NONE;
- final int hyphenationMode;
- if (needHyphenation) {
- hyphenationMode = isFastHyphenation(params.getHyphenationFrequency())
- ? MeasuredText.Builder.HYPHENATION_MODE_FAST :
- MeasuredText.Builder.HYPHENATION_MODE_NORMAL;
- } else {
- hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
+ if (TRACE_PCT) {
+ Trace.beginSection("PrecomputedText#createMeasuredParagraphsFromPrecomputedText");
+ Trace.setCounter("PrecomputedText#textCharCount", pct.length());
}
- LineBreakConfig config = params.getLineBreakConfig();
- if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
- && pct.getParagraphCount() != 1) {
- // If the text has multiple paragraph, resolve line break word style auto to none.
- config = new LineBreakConfig.Builder()
- .merge(config)
- .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
- .build();
+ try {
+ final boolean needHyphenation =
+ params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
+ && params.getHyphenationFrequency()
+ != Layout.HYPHENATION_FREQUENCY_NONE;
+ final int hyphenationMode;
+ if (needHyphenation) {
+ hyphenationMode = isFastHyphenation(params.getHyphenationFrequency())
+ ? MeasuredText.Builder.HYPHENATION_MODE_FAST :
+ MeasuredText.Builder.HYPHENATION_MODE_NORMAL;
+ } else {
+ hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
+ }
+ LineBreakConfig config = params.getLineBreakConfig();
+ if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
+ && pct.getParagraphCount() != 1) {
+ // If the text has multiple paragraph, resolve line break word style auto to none.
+ config = new LineBreakConfig.Builder()
+ .merge(config)
+ .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
+ .build();
+ }
+ ArrayList<ParagraphInfo> result = new ArrayList<>();
+ for (int i = 0; i < pct.getParagraphCount(); ++i) {
+ final int paraStart = pct.getParagraphStart(i);
+ final int paraEnd = pct.getParagraphEnd(i);
+ result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
+ params.getTextPaint(), config, pct, paraStart, paraEnd,
+ params.getTextDirection(), hyphenationMode, computeLayout, true,
+ pct.getMeasuredParagraph(i), null /* no recycle */)));
+ }
+ return result.toArray(new ParagraphInfo[result.size()]);
+ } finally {
+ if (TRACE_PCT) {
+ Trace.endSection();
+ }
}
- ArrayList<ParagraphInfo> result = new ArrayList<>();
- for (int i = 0; i < pct.getParagraphCount(); ++i) {
- final int paraStart = pct.getParagraphStart(i);
- final int paraEnd = pct.getParagraphEnd(i);
- result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
- params.getTextPaint(), config, pct, paraStart, paraEnd,
- params.getTextDirection(), hyphenationMode, computeLayout, true,
- pct.getMeasuredParagraph(i), null /* no recycle */)));
- }
- return result.toArray(new ParagraphInfo[result.size()]);
}
/** @hide */
@@ -483,53 +499,65 @@
@NonNull CharSequence text, @NonNull Params params,
@IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean computeLayout,
boolean computeBounds) {
- ArrayList<ParagraphInfo> result = new ArrayList<>();
-
- Preconditions.checkNotNull(text);
- Preconditions.checkNotNull(params);
- final boolean needHyphenation = params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
- && params.getHyphenationFrequency() != Layout.HYPHENATION_FREQUENCY_NONE;
- final int hyphenationMode;
- if (needHyphenation) {
- hyphenationMode = isFastHyphenation(params.getHyphenationFrequency())
- ? MeasuredText.Builder.HYPHENATION_MODE_FAST :
- MeasuredText.Builder.HYPHENATION_MODE_NORMAL;
- } else {
- hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
+ if (TRACE_PCT) {
+ Trace.beginSection("PrecomputedText#createMeasuredParagraphs");
+ Trace.setCounter("PrecomputedText#textCharCount", text.length());
}
+ try {
+ ArrayList<ParagraphInfo> result = new ArrayList<>();
- LineBreakConfig config = null;
- int paraEnd = 0;
- for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
- paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
- if (paraEnd < 0) {
- // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph
- // end.
- paraEnd = end;
+ Preconditions.checkNotNull(text);
+ Preconditions.checkNotNull(params);
+ final boolean needHyphenation =
+ params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
+ && params.getHyphenationFrequency()
+ != Layout.HYPHENATION_FREQUENCY_NONE;
+ final int hyphenationMode;
+ if (needHyphenation) {
+ hyphenationMode = isFastHyphenation(params.getHyphenationFrequency())
+ ? MeasuredText.Builder.HYPHENATION_MODE_FAST :
+ MeasuredText.Builder.HYPHENATION_MODE_NORMAL;
} else {
- paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph.
+ hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
}
- if (config == null) {
- config = params.getLineBreakConfig();
- if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
- && !(paraStart == start && paraEnd == end)) {
- // If the text has multiple paragraph, resolve line break word style auto to
- // none.
- config = new LineBreakConfig.Builder()
- .merge(config)
- .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
- .build();
+ LineBreakConfig config = null;
+ int paraEnd = 0;
+ for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
+ paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
+ if (paraEnd < 0) {
+ // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph
+ // end.
+ paraEnd = end;
+ } else {
+ paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph.
}
- }
- result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
- params.getTextPaint(), config, text, paraStart, paraEnd,
- params.getTextDirection(), hyphenationMode, computeLayout, computeBounds,
- null /* no hint */,
- null /* no recycle */)));
+ if (config == null) {
+ config = params.getLineBreakConfig();
+ if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
+ && !(paraStart == start && paraEnd == end)) {
+ // If the text has multiple paragraph, resolve line break word style auto to
+ // none.
+ config = new LineBreakConfig.Builder()
+ .merge(config)
+ .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
+ .build();
+ }
+ }
+
+ result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
+ params.getTextPaint(), config, text, paraStart, paraEnd,
+ params.getTextDirection(), hyphenationMode, computeLayout, computeBounds,
+ null /* no hint */,
+ null /* no recycle */)));
+ }
+ return result.toArray(new ParagraphInfo[result.size()]);
+ } finally {
+ if (TRACE_PCT) {
+ Trace.endSection();
+ }
}
- return result.toArray(new ParagraphInfo[result.size()]);
}
// Use PrecomputedText.create instead.
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 3dd3a9e..d1b14d1 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -542,10 +542,20 @@
*/
@NonNull
public StaticLayout build() {
- StaticLayout result = new StaticLayout(this, mIncludePad, mEllipsize != null
- ? COLUMNS_ELLIPSIZE : COLUMNS_NORMAL);
- Builder.recycle(this);
- return result;
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("StaticLayout#build");
+ Trace.setCounter("StaticLayout#textLength", mText.length());
+ }
+ try {
+ StaticLayout result = new StaticLayout(this, mIncludePad, mEllipsize != null
+ ? COLUMNS_ELLIPSIZE : COLUMNS_NORMAL);
+ Builder.recycle(this);
+ return result;
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
+ }
+ }
}
/**
@@ -562,16 +572,21 @@
*/
/* package */ @NonNull StaticLayout buildPartialStaticLayoutForDynamicLayout(
boolean trackpadding, StaticLayout recycle) {
- if (recycle == null) {
- recycle = new StaticLayout();
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("StaticLayout#forDynamicLayout");
+ Trace.setCounter("StaticLayout#textLength", mText.length());
}
- Trace.beginSection("Generating StaticLayout For DynamicLayout");
try {
+ if (recycle == null) {
+ recycle = new StaticLayout();
+ }
recycle.generate(this, mIncludePad, trackpadding);
+ return recycle;
} finally {
- Trace.endSection();
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
+ }
}
- return recycle;
}
private CharSequence mText;
@@ -727,12 +742,7 @@
mLeftIndents = b.mLeftIndents;
mRightIndents = b.mRightIndents;
- Trace.beginSection("Constructing StaticLayout");
- try {
- generate(b, b.mIncludePad, trackPadding);
- } finally {
- Trace.endSection();
- }
+ generate(b, b.mIncludePad, trackPadding);
}
private static int getBaseHyphenationFrequency(int frequency) {
@@ -842,14 +852,23 @@
case PrecomputedText.Params.UNUSABLE:
break;
case PrecomputedText.Params.NEED_RECOMPUTE:
- final PrecomputedText.Params newParams =
- new PrecomputedText.Params.Builder(paint)
- .setBreakStrategy(b.mBreakStrategy)
- .setHyphenationFrequency(b.mHyphenationFrequency)
- .setTextDirection(textDir)
- .setLineBreakConfig(b.mLineBreakConfig)
- .build();
- precomputed = PrecomputedText.create(precomputed, newParams);
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("StaticLayout#recomputePct");
+ }
+ try {
+ final PrecomputedText.Params newParams =
+ new PrecomputedText.Params.Builder(paint)
+ .setBreakStrategy(b.mBreakStrategy)
+ .setHyphenationFrequency(b.mHyphenationFrequency)
+ .setTextDirection(textDir)
+ .setLineBreakConfig(b.mLineBreakConfig)
+ .build();
+ precomputed = PrecomputedText.create(precomputed, newParams);
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
+ }
+ }
paragraphInfo = precomputed.getParagraphInfo();
break;
case PrecomputedText.Params.USABLE:
@@ -860,232 +879,261 @@
}
if (paragraphInfo == null) {
- final PrecomputedText.Params param = new PrecomputedText.Params(paint,
- b.mLineBreakConfig, textDir, b.mBreakStrategy, b.mHyphenationFrequency);
- paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart,
- bufEnd, false /* computeLayout */, b.mCalculateBounds);
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("StaticLayout#computePct");
+ }
+ try {
+ final PrecomputedText.Params param = new PrecomputedText.Params(paint,
+ b.mLineBreakConfig, textDir, b.mBreakStrategy, b.mHyphenationFrequency);
+ paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart,
+ bufEnd, false /* computeLayout */, b.mCalculateBounds);
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
+ }
+ }
}
for (int paraIndex = 0; paraIndex < paragraphInfo.length; paraIndex++) {
- final int paraStart = paraIndex == 0
- ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;
- final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("StaticLayout#processParagraph");
+ Trace.setCounter("StaticLayout#paragraph", paraIndex);
+ }
+ try {
+ final int paraStart = paraIndex == 0
+ ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;
+ final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;
- int firstWidthLineCount = 1;
- int firstWidth = outerWidth;
- int restWidth = outerWidth;
+ int firstWidthLineCount = 1;
+ int firstWidth = outerWidth;
+ int restWidth = outerWidth;
- LineHeightSpan[] chooseHt = null;
- if (spanned != null) {
- LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
- LeadingMarginSpan.class);
- for (int i = 0; i < sp.length; i++) {
- LeadingMarginSpan lms = sp[i];
- firstWidth -= sp[i].getLeadingMargin(true);
- restWidth -= sp[i].getLeadingMargin(false);
+ LineHeightSpan[] chooseHt = null;
+ if (spanned != null) {
+ LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
+ LeadingMarginSpan.class);
+ for (int i = 0; i < sp.length; i++) {
+ LeadingMarginSpan lms = sp[i];
+ firstWidth -= sp[i].getLeadingMargin(true);
+ restWidth -= sp[i].getLeadingMargin(false);
- // LeadingMarginSpan2 is odd. The count affects all
- // leading margin spans, not just this particular one
- if (lms instanceof LeadingMarginSpan2) {
- LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
- firstWidthLineCount = Math.max(firstWidthLineCount,
- lms2.getLeadingMarginLineCount());
+ // LeadingMarginSpan2 is odd. The count affects all
+ // leading margin spans, not just this particular one
+ if (lms instanceof LeadingMarginSpan2) {
+ LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
+ firstWidthLineCount = Math.max(firstWidthLineCount,
+ lms2.getLeadingMarginLineCount());
+ }
+ }
+
+ chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
+
+ if (chooseHt.length == 0) {
+ chooseHt = null; // So that out() would not assume it has any contents
+ } else {
+ if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
+ chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
+ }
+
+ for (int i = 0; i < chooseHt.length; i++) {
+ int o = spanned.getSpanStart(chooseHt[i]);
+
+ if (o < paraStart) {
+ // starts in this layout, before the
+ // current paragraph
+
+ chooseHtv[i] = getLineTop(getLineForOffset(o));
+ } else {
+ // starts in this paragraph
+
+ chooseHtv[i] = v;
+ }
+ }
+ }
+ }
+ // tab stop locations
+ float[] variableTabStops = null;
+ if (spanned != null) {
+ TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
+ paraEnd, TabStopSpan.class);
+ if (spans.length > 0) {
+ float[] stops = new float[spans.length];
+ for (int i = 0; i < spans.length; i++) {
+ stops[i] = (float) spans[i].getTabStop();
+ }
+ Arrays.sort(stops, 0, stops.length);
+ variableTabStops = stops;
}
}
- chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
+ final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured;
+ final char[] chs = measuredPara.getChars();
+ final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
+ final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
- if (chooseHt.length == 0) {
- chooseHt = null; // So that out() would not assume it has any contents
- } else {
- if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
- chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
+ constraints.setWidth(restWidth);
+ constraints.setIndent(firstWidth, firstWidthLineCount);
+ constraints.setTabStops(variableTabStops, TAB_INCREMENT);
+
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("LineBreaker#computeLineBreaks");
+ }
+ LineBreaker.Result res;
+ try {
+ res = lineBreaker.computeLineBreaks(
+ measuredPara.getMeasuredText(), constraints, mLineCount);
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
}
+ }
+ int breakCount = res.getLineCount();
+ if (lineBreakCapacity < breakCount) {
+ lineBreakCapacity = breakCount;
+ breaks = new int[lineBreakCapacity];
+ lineWidths = new float[lineBreakCapacity];
+ ascents = new float[lineBreakCapacity];
+ descents = new float[lineBreakCapacity];
+ hasTabs = new boolean[lineBreakCapacity];
+ hyphenEdits = new int[lineBreakCapacity];
+ }
- for (int i = 0; i < chooseHt.length; i++) {
- int o = spanned.getSpanStart(chooseHt[i]);
+ for (int i = 0; i < breakCount; ++i) {
+ breaks[i] = res.getLineBreakOffset(i);
+ lineWidths[i] = res.getLineWidth(i);
+ ascents[i] = res.getLineAscent(i);
+ descents[i] = res.getLineDescent(i);
+ hasTabs[i] = res.hasLineTab(i);
+ hyphenEdits[i] =
+ packHyphenEdit(res.getStartLineHyphenEdit(i), res.getEndLineHyphenEdit(i));
+ }
- if (o < paraStart) {
- // starts in this layout, before the
- // current paragraph
-
- chooseHtv[i] = getLineTop(getLineForOffset(o));
+ final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
+ final boolean ellipsisMayBeApplied = ellipsize != null
+ && (ellipsize == TextUtils.TruncateAt.END
+ || (mMaximumVisibleLineCount == 1
+ && ellipsize != TextUtils.TruncateAt.MARQUEE));
+ if (0 < remainingLineCount && remainingLineCount < breakCount
+ && ellipsisMayBeApplied) {
+ // Calculate width
+ float width = 0;
+ boolean hasTab = false; // XXX May need to also have starting hyphen edit
+ for (int i = remainingLineCount - 1; i < breakCount; i++) {
+ if (i == breakCount - 1) {
+ width += lineWidths[i];
} else {
- // starts in this paragraph
+ for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
+ width += measuredPara.getCharWidthAt(j);
+ }
+ }
+ hasTab |= hasTabs[i];
+ }
+ // Treat the last line and overflowed lines as a single line.
+ breaks[remainingLineCount - 1] = breaks[breakCount - 1];
+ lineWidths[remainingLineCount - 1] = width;
+ hasTabs[remainingLineCount - 1] = hasTab;
- chooseHtv[i] = v;
+ breakCount = remainingLineCount;
+ }
+
+ // here is the offset of the starting character of the line we are currently
+ // measuring
+ int here = paraStart;
+
+ int fmTop = defaultTop;
+ int fmBottom = defaultBottom;
+ int fmAscent = defaultAscent;
+ int fmDescent = defaultDescent;
+ int fmCacheIndex = 0;
+ int spanEndCacheIndex = 0;
+ int breakIndex = 0;
+ for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
+ // retrieve end of span
+ spanEnd = spanEndCache[spanEndCacheIndex++];
+
+ // retrieve cached metrics, order matches above
+ fm.top = fmCache[fmCacheIndex * 4 + 0];
+ fm.bottom = fmCache[fmCacheIndex * 4 + 1];
+ fm.ascent = fmCache[fmCacheIndex * 4 + 2];
+ fm.descent = fmCache[fmCacheIndex * 4 + 3];
+ fmCacheIndex++;
+
+ if (fm.top < fmTop) {
+ fmTop = fm.top;
+ }
+ if (fm.ascent < fmAscent) {
+ fmAscent = fm.ascent;
+ }
+ if (fm.descent > fmDescent) {
+ fmDescent = fm.descent;
+ }
+ if (fm.bottom > fmBottom) {
+ fmBottom = fm.bottom;
+ }
+
+ // skip breaks ending before current span range
+ while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
+ breakIndex++;
+ }
+
+ while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
+ int endPos = paraStart + breaks[breakIndex];
+
+ boolean moreChars = (endPos < bufEnd);
+
+ final int ascent = isFallbackLineSpacing
+ ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
+ : fmAscent;
+ final int descent = isFallbackLineSpacing
+ ? Math.max(fmDescent, Math.round(descents[breakIndex]))
+ : fmDescent;
+
+ // The fallback ascent/descent may be larger than top/bottom of the default
+ // font metrics. Adjust top/bottom with ascent/descent for avoiding
+ // unexpected clipping.
+ if (isFallbackLineSpacing) {
+ if (ascent < fmTop) {
+ fmTop = ascent;
+ }
+ if (descent > fmBottom) {
+ fmBottom = descent;
+ }
+ }
+
+ v = out(source, here, endPos,
+ ascent, descent, fmTop, fmBottom,
+ v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
+ hasTabs[breakIndex], hyphenEdits[breakIndex], needMultiply,
+ measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, chs,
+ paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
+ paint, moreChars);
+
+ if (endPos < spanEnd) {
+ // preserve metrics for current span
+ fmTop = Math.min(defaultTop, fm.top);
+ fmBottom = Math.max(defaultBottom, fm.bottom);
+ fmAscent = Math.min(defaultAscent, fm.ascent);
+ fmDescent = Math.max(defaultDescent, fm.descent);
+ } else {
+ fmTop = fmBottom = fmAscent = fmDescent = 0;
+ }
+
+ here = endPos;
+ breakIndex++;
+
+ if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
+ return;
}
}
}
- }
- // tab stop locations
- float[] variableTabStops = null;
- if (spanned != null) {
- TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
- paraEnd, TabStopSpan.class);
- if (spans.length > 0) {
- float[] stops = new float[spans.length];
- for (int i = 0; i < spans.length; i++) {
- stops[i] = (float) spans[i].getTabStop();
- }
- Arrays.sort(stops, 0, stops.length);
- variableTabStops = stops;
+
+ if (paraEnd == bufEnd) {
+ break;
}
- }
-
- final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured;
- final char[] chs = measuredPara.getChars();
- final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
- final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
-
- constraints.setWidth(restWidth);
- constraints.setIndent(firstWidth, firstWidthLineCount);
- constraints.setTabStops(variableTabStops, TAB_INCREMENT);
-
- LineBreaker.Result res = lineBreaker.computeLineBreaks(
- measuredPara.getMeasuredText(), constraints, mLineCount);
- int breakCount = res.getLineCount();
- if (lineBreakCapacity < breakCount) {
- lineBreakCapacity = breakCount;
- breaks = new int[lineBreakCapacity];
- lineWidths = new float[lineBreakCapacity];
- ascents = new float[lineBreakCapacity];
- descents = new float[lineBreakCapacity];
- hasTabs = new boolean[lineBreakCapacity];
- hyphenEdits = new int[lineBreakCapacity];
- }
-
- for (int i = 0; i < breakCount; ++i) {
- breaks[i] = res.getLineBreakOffset(i);
- lineWidths[i] = res.getLineWidth(i);
- ascents[i] = res.getLineAscent(i);
- descents[i] = res.getLineDescent(i);
- hasTabs[i] = res.hasLineTab(i);
- hyphenEdits[i] =
- packHyphenEdit(res.getStartLineHyphenEdit(i), res.getEndLineHyphenEdit(i));
- }
-
- final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
- final boolean ellipsisMayBeApplied = ellipsize != null
- && (ellipsize == TextUtils.TruncateAt.END
- || (mMaximumVisibleLineCount == 1
- && ellipsize != TextUtils.TruncateAt.MARQUEE));
- if (0 < remainingLineCount && remainingLineCount < breakCount
- && ellipsisMayBeApplied) {
- // Calculate width
- float width = 0;
- boolean hasTab = false; // XXX May need to also have starting hyphen edit
- for (int i = remainingLineCount - 1; i < breakCount; i++) {
- if (i == breakCount - 1) {
- width += lineWidths[i];
- } else {
- for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
- width += measuredPara.getCharWidthAt(j);
- }
- }
- hasTab |= hasTabs[i];
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
}
- // Treat the last line and overflowed lines as a single line.
- breaks[remainingLineCount - 1] = breaks[breakCount - 1];
- lineWidths[remainingLineCount - 1] = width;
- hasTabs[remainingLineCount - 1] = hasTab;
-
- breakCount = remainingLineCount;
- }
-
- // here is the offset of the starting character of the line we are currently
- // measuring
- int here = paraStart;
-
- int fmTop = defaultTop;
- int fmBottom = defaultBottom;
- int fmAscent = defaultAscent;
- int fmDescent = defaultDescent;
- int fmCacheIndex = 0;
- int spanEndCacheIndex = 0;
- int breakIndex = 0;
- for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
- // retrieve end of span
- spanEnd = spanEndCache[spanEndCacheIndex++];
-
- // retrieve cached metrics, order matches above
- fm.top = fmCache[fmCacheIndex * 4 + 0];
- fm.bottom = fmCache[fmCacheIndex * 4 + 1];
- fm.ascent = fmCache[fmCacheIndex * 4 + 2];
- fm.descent = fmCache[fmCacheIndex * 4 + 3];
- fmCacheIndex++;
-
- if (fm.top < fmTop) {
- fmTop = fm.top;
- }
- if (fm.ascent < fmAscent) {
- fmAscent = fm.ascent;
- }
- if (fm.descent > fmDescent) {
- fmDescent = fm.descent;
- }
- if (fm.bottom > fmBottom) {
- fmBottom = fm.bottom;
- }
-
- // skip breaks ending before current span range
- while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
- breakIndex++;
- }
-
- while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
- int endPos = paraStart + breaks[breakIndex];
-
- boolean moreChars = (endPos < bufEnd);
-
- final int ascent = isFallbackLineSpacing
- ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
- : fmAscent;
- final int descent = isFallbackLineSpacing
- ? Math.max(fmDescent, Math.round(descents[breakIndex]))
- : fmDescent;
-
- // The fallback ascent/descent may be larger than top/bottom of the default font
- // metrics. Adjust top/bottom with ascent/descent for avoiding unexpected
- // clipping.
- if (isFallbackLineSpacing) {
- if (ascent < fmTop) {
- fmTop = ascent;
- }
- if (descent > fmBottom) {
- fmBottom = descent;
- }
- }
-
- v = out(source, here, endPos,
- ascent, descent, fmTop, fmBottom,
- v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
- hasTabs[breakIndex], hyphenEdits[breakIndex], needMultiply,
- measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, chs,
- paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
- paint, moreChars);
-
- if (endPos < spanEnd) {
- // preserve metrics for current span
- fmTop = Math.min(defaultTop, fm.top);
- fmBottom = Math.max(defaultBottom, fm.bottom);
- fmAscent = Math.min(defaultAscent, fm.ascent);
- fmDescent = Math.max(defaultDescent, fm.descent);
- } else {
- fmTop = fmBottom = fmAscent = fmDescent = 0;
- }
-
- here = endPos;
- breakIndex++;
-
- if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
- return;
- }
- }
- }
-
- if (paraEnd == bufEnd) {
- break;
}
}
@@ -1179,9 +1227,18 @@
(!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
ellipsize == TextUtils.TruncateAt.END);
if (doEllipsis) {
- calculateEllipsis(start, end, measured, widthStart,
- ellipsisWidth, ellipsize, j,
- textWidth, paint, forceEllipsis);
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("StaticLayout#calculateEllipsis");
+ }
+ try {
+ calculateEllipsis(start, end, measured, widthStart,
+ ellipsisWidth, ellipsize, j,
+ textWidth, paint, forceEllipsis);
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
+ }
+ }
} else {
mLines[mColumns * j + ELLIPSIS_START] = 0;
mLines[mColumns * j + ELLIPSIS_COUNT] = 0;
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index bde9c77..a439478 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -28,6 +28,7 @@
import android.graphics.text.PositionedGlyphs;
import android.graphics.text.TextRunShaper;
import android.os.Build;
+import android.os.Trace;
import android.text.Layout.Directions;
import android.text.Layout.TabStops;
import android.text.style.CharacterStyle;
@@ -56,6 +57,8 @@
public class TextLine {
private static final boolean DEBUG = false;
+ private static final boolean TRACE_TEXTLINE = Build.isDebuggable();
+
private static final char TAB_CHAR = '\t';
private TextPaint mPaint;
@@ -430,28 +433,37 @@
* @param bottom the bottom of the line
*/
void draw(Canvas c, float x, int top, int y, int bottom) {
- float h = 0;
- final int runCount = mDirections.getRunCount();
- for (int runIndex = 0; runIndex < runCount; runIndex++) {
- final int runStart = mDirections.getRunStart(runIndex);
- if (runStart > mLen) break;
- final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
- final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+ if (TRACE_TEXTLINE) {
+ Trace.beginSection("TextLine#draw");
+ }
+ try {
+ float h = 0;
+ final int runCount = mDirections.getRunCount();
+ for (int runIndex = 0; runIndex < runCount; runIndex++) {
+ final int runStart = mDirections.getRunStart(runIndex);
+ if (runStart > mLen) break;
+ final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
+ final boolean runIsRtl = mDirections.isRunRtl(runIndex);
- final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
+ final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
- int segStart = runStart;
- for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
- if (j == runLimit || charAt(j) == TAB_CHAR) {
- h += drawRun(c, segStart, j, runIsRtl, x + h, top, y, bottom,
- runIndex != (runCount - 1) || j != mLen, runFlag);
+ int segStart = runStart;
+ for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
+ if (j == runLimit || charAt(j) == TAB_CHAR) {
+ h += drawRun(c, segStart, j, runIsRtl, x + h, top, y, bottom,
+ runIndex != (runCount - 1) || j != mLen, runFlag);
- if (j != runLimit) { // charAt(j) == TAB_CHAR
- h = mDir * nextTab(h * mDir);
+ if (j != runLimit) { // charAt(j) == TAB_CHAR
+ h = mDir * nextTab(h * mDir);
+ }
+ segStart = j + 1;
}
- segStart = j + 1;
}
}
+ } finally {
+ if (TRACE_TEXTLINE) {
+ Trace.endSection();
+ }
}
}
@@ -564,63 +576,76 @@
*/
public float measure(@IntRange(from = 0) int offset, boolean trailing,
@NonNull FontMetricsInt fmi, @Nullable RectF drawBounds, @Nullable LineInfo lineInfo) {
- if (offset > mLen) {
- throw new IndexOutOfBoundsException(
- "offset(" + offset + ") should be less than line limit(" + mLen + ")");
+ if (TRACE_TEXTLINE) {
+ Trace.beginSection("TextLine#measure");
}
- if (lineInfo != null) {
- lineInfo.setClusterCount(0);
- }
- final int target = trailing ? offset - 1 : offset;
- if (target < 0) {
- return 0;
- }
+ try {
+ if (offset > mLen) {
+ throw new IndexOutOfBoundsException(
+ "offset(" + offset + ") should be less than line limit(" + mLen + ")");
+ }
+ if (lineInfo != null) {
+ lineInfo.setClusterCount(0);
+ }
+ final int target = trailing ? offset - 1 : offset;
+ if (target < 0) {
+ return 0;
+ }
- float h = 0;
- final int runCount = mDirections.getRunCount();
- for (int runIndex = 0; runIndex < runCount; runIndex++) {
- final int runStart = mDirections.getRunStart(runIndex);
- if (runStart > mLen) break;
- final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
- final boolean runIsRtl = mDirections.isRunRtl(runIndex);
- final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
+ float h = 0;
+ final int runCount = mDirections.getRunCount();
+ for (int runIndex = 0; runIndex < runCount; runIndex++) {
+ final int runStart = mDirections.getRunStart(runIndex);
+ if (runStart > mLen) break;
+ final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
+ final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+ final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
- int segStart = runStart;
- for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
- if (j == runLimit || charAt(j) == TAB_CHAR) {
- final boolean targetIsInThisSegment = target >= segStart && target < j;
- final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
+ int segStart = runStart;
+ for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
+ if (j == runLimit || charAt(j) == TAB_CHAR) {
+ final boolean targetIsInThisSegment = target >= segStart && target < j;
+ final boolean sameDirection =
+ (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
- if (targetIsInThisSegment && sameDirection) {
- return h + measureRun(segStart, offset, j, runIsRtl, fmi, drawBounds, null,
- 0, h, lineInfo, runFlag);
- }
-
- final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi, drawBounds,
- null, 0, h, lineInfo, runFlag);
- h += sameDirection ? segmentWidth : -segmentWidth;
-
- if (targetIsInThisSegment) {
- return h + measureRun(segStart, offset, j, runIsRtl, null, null, null, 0,
- h, lineInfo, runFlag);
- }
-
- if (j != runLimit) { // charAt(j) == TAB_CHAR
- if (offset == j) {
- return h;
+ if (targetIsInThisSegment && sameDirection) {
+ return h + measureRun(segStart, offset, j, runIsRtl, fmi, drawBounds,
+ null,
+ 0, h, lineInfo, runFlag);
}
- h = mDir * nextTab(h * mDir);
- if (target == j) {
- return h;
- }
- }
- segStart = j + 1;
+ final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi,
+ drawBounds,
+ null, 0, h, lineInfo, runFlag);
+ h += sameDirection ? segmentWidth : -segmentWidth;
+
+ if (targetIsInThisSegment) {
+ return h + measureRun(segStart, offset, j, runIsRtl, null, null, null,
+ 0,
+ h, lineInfo, runFlag);
+ }
+
+ if (j != runLimit) { // charAt(j) == TAB_CHAR
+ if (offset == j) {
+ return h;
+ }
+ h = mDir * nextTab(h * mDir);
+ if (target == j) {
+ return h;
+ }
+ }
+
+ segStart = j + 1;
+ }
}
}
- }
- return h;
+ return h;
+ } finally {
+ if (TRACE_TEXTLINE) {
+ Trace.endSection();
+ }
+ }
}
/**
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index f68fcab9..aff1d4a 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -119,3 +119,10 @@
is_fixed_read_only: true
bug: "324676775"
}
+
+flag {
+ name: "handwriting_cursor_position"
+ namespace: "text"
+ description: "When handwriting is initiated in an unfocused TextView, cursor is placed at the end of the closest paragraph."
+ bug: "323376217"
+}
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 66655fc..29c8350 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -16,6 +16,8 @@
package android.view;
+import static com.android.text.flags.Flags.handwritingCursorPosition;
+
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -557,7 +559,8 @@
}
private void requestFocusWithoutReveal(View view) {
- if (view instanceof EditText editText && !mState.mStylusDownWithinEditorBounds) {
+ if (!handwritingCursorPosition() && view instanceof EditText editText
+ && !mState.mStylusDownWithinEditorBounds) {
// If the stylus down point was inside the EditText's bounds, then the EditText will
// automatically set its cursor position nearest to the stylus down point when it
// gains focus. If the stylus down point was outside the EditText's bounds (within
@@ -576,6 +579,17 @@
} else {
view.requestFocus();
}
+ if (handwritingCursorPosition() && view instanceof EditText editText) {
+ // Move the cursor to the end of the paragraph closest to the stylus down point.
+ view.getLocationInWindow(mTempLocation);
+ int line = editText.getLineAtCoordinate(mState.mStylusDownY - mTempLocation[1]);
+ int paragraphEnd = TextUtils.indexOf(editText.getText(), '\n',
+ editText.getLayout().getLineStart(line));
+ if (paragraphEnd < 0) {
+ paragraphEnd = editText.getText().length();
+ }
+ editText.setSelection(paragraphEnd);
+ }
}
/**
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index a098e4d..4f5b51d 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -2631,7 +2631,10 @@
* Threshold values that are sent with
* {@link Transaction#setTrustedPresentationCallback(SurfaceControl,
* TrustedPresentationThresholds, Executor, Consumer)}
+ *
+ * @deprecated Use {@link android.window.TrustedPresentationThresholds} instead.
*/
+ @Deprecated
public static final class TrustedPresentationThresholds {
private final float mMinAlpha;
private final float mMinFractionRendered;
@@ -4451,8 +4454,7 @@
* <tr><td>o</td><td>x</td><td>x</td><td>x</td></tr>
* </table>
* </blockquote>
- *
- *<p>
+ * <p>
* We first start by computing fr=xscale*yscale=0.9*0.9=0.81, indicating
* that "81%" of the pixels were rendered. This corresponds to what was 100
* pixels being displayed in 81 pixels. This is somewhat of an abuse of
@@ -4469,6 +4471,7 @@
* be somewhat arbitrary, and so there are some somewhat arbitrary decisions in
* this API as well.
* <p>
+ *
* @param sc The {@link SurfaceControl} to set the callback on
* @param thresholds The {@link TrustedPresentationThresholds} that will specify when the to
* invoke the callback.
@@ -4477,7 +4480,11 @@
* exited the threshold.
* @return This transaction
* @see TrustedPresentationThresholds
+ * @deprecated Use
+ * {@link WindowManager#registerTrustedPresentationListener(IBinder,
+ * android.window.TrustedPresentationThresholds, Executor, Consumer)} instead.
*/
+ @Deprecated
@NonNull
public Transaction setTrustedPresentationCallback(@NonNull SurfaceControl sc,
@NonNull TrustedPresentationThresholds thresholds, @NonNull Executor executor,
@@ -4506,7 +4513,10 @@
*
* @param sc The SurfaceControl that the callback should be cleared from
* @return This transaction
+ * @deprecated Use {@link WindowManager#unregisterTrustedPresentationListener(Consumer)}
+ * instead.
*/
+ @Deprecated
@NonNull
public Transaction clearTrustedPresentationCallback(@NonNull SurfaceControl sc) {
checkPreconditions(sc);
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 6f757df..04ec4d0 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -583,13 +583,16 @@
* associated host {@link InputTransferToken}.
*
* @return Whether the touch stream was transferred.
+ * @deprecated Use {@link WindowManager#transferTouchGesture(InputTransferToken,
+ * InputTransferToken)} instead.
*/
+ @Deprecated
public boolean transferTouchGestureToHost() {
if (mViewRoot == null) {
return false;
}
- final WindowManager wm =
- (WindowManager) mViewRoot.mContext.getSystemService(Context.WINDOW_SERVICE);
+ final WindowManager wm = (WindowManager) mViewRoot.mContext.getSystemService(
+ Context.WINDOW_SERVICE);
InputTransferToken embeddedToken = getInputTransferToken();
InputTransferToken hostToken = mWm.mHostInputTransferToken;
if (embeddedToken == null || hostToken == null) {
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index d494e28..c95d6ff 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1946,7 +1946,9 @@
* be passed from the host process to the client process.
*
* @return The token
+ * @deprecated Use {@link AttachedSurfaceControl#getInputTransferToken()} instead.
*/
+ @Deprecated
public @Nullable IBinder getHostToken() {
final ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot == null) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a5ff48f..0ac8936 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2372,6 +2372,12 @@
protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
/**
+ * This indicates that the frame rate category was chosen for an unknown reason.
+ * @hide
+ */
+ public static final int FRAME_RATE_CATEGORY_REASON_UNKNOWN = 0x0000_0000;
+
+ /**
* This indicates that the frame rate category was chosen because it was a small area update.
* @hide
*/
@@ -2402,9 +2408,24 @@
*/
public static final int FRAME_RATE_CATEGORY_REASON_INVALID = 0x0500_0000;
+ /**
+ * This indicates that the frame rate category was chosen because the view has a velocity
+ * @hide
+ */
+ public static final int FRAME_RATE_CATEGORY_REASON_VELOCITY = 0x0600_0000;
+
+ /**
+ * This indicates that the frame rate category was chosen because it is idle.
+ * @hide
+ */
+ public static final int FRAME_RATE_CATEGORY_REASON_IDLE = 0x0700_0000;
+
private static final int FRAME_RATE_CATEGORY_REASON_MASK = 0xFFFF_0000;
- private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
+ /**
+ * @hide
+ */
+ protected static boolean sToolkitSetFrameRateReadOnlyFlagValue;
private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
// Used to set frame rate compatibility.
@Surface.FrameRateCompatibility int mFrameRateCompatibility =
@@ -33778,6 +33799,10 @@
return;
}
}
+ if (sToolkitMetricsForFrameRateDecisionFlagValue) {
+ float sizePercentage = getSizePercentage();
+ viewRootImpl.recordViewPercentage(sizePercentage);
+ }
int frameRateCategory;
if (Float.isNaN(mPreferredFrameRate)) {
frameRateCategory = calculateFrameRateCategory(width, height);
@@ -33806,11 +33831,8 @@
}
int category = frameRateCategory & ~FRAME_RATE_CATEGORY_REASON_MASK;
- if (sToolkitMetricsForFrameRateDecisionFlagValue) {
- int reason = frameRateCategory & FRAME_RATE_CATEGORY_REASON_MASK;
- viewRootImpl.recordCategory(category, reason, this);
- }
- viewRootImpl.votePreferredFrameRateCategory(category);
+ int reason = frameRateCategory & FRAME_RATE_CATEGORY_REASON_MASK;
+ viewRootImpl.votePreferredFrameRateCategory(category, reason, this);
mLastFrameRateCategory = frameRateCategory;
}
}
@@ -33906,9 +33928,9 @@
}
/**
- * This function is mainly used for migrating infrequent layer lagic
+ * This function is mainly used for migrating infrequent layer logic
* from SurfaceFlinger to Toolkit.
- * The infrequent layter logic includes:
+ * The infrequent layer logic includes:
* - NORMAL for infrequent update: FT2-FT1 > 100 && FT3-FT2 > 100.
* - HIGH/NORMAL based on size for frequent update: (FT3-FT2) + (FT2 - FT1) < 100.
* - otherwise, use the previous category value.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 6f178bb..8c3cf5f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -32,11 +32,14 @@
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_UNKNOWN;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_IDLE;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_INVALID;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_LARGE;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_REQUESTED;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_SMALL;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_VELOCITY;
import static android.view.View.PFLAG_DRAW_ANIMATION;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -847,6 +850,8 @@
private boolean mInsetsAnimationRunning;
private long mPreviousFrameDrawnTime = -1;
+ // The largest view size percentage to the display size. Used on trace to collect metric.
+ private float mLargestChildPercentage = 0.0f;
// The reason the category was changed.
private int mFrameRateCategoryChangeReason = 0;
private String mFrameRateCategoryView;
@@ -4854,6 +4859,10 @@
long fps = NANOS_PER_SEC / timeDiff;
Trace.setCounter(mFpsTraceName, fps);
mPreviousFrameDrawnTime = expectedDrawnTime;
+
+ long percentage = (long) (mLargestChildPercentage * 100);
+ Trace.setCounter(mLargestViewTraceName, percentage);
+ mLargestChildPercentage = 0.0f;
}
private void reportDrawFinished(@Nullable Transaction t, int seqId) {
@@ -6540,6 +6549,8 @@
case MSG_CHECK_INVALIDATION_IDLE:
if (!mHasInvalidation && !mIsFrameRateBoosting && !mIsTouchBoosting) {
mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+ mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_IDLE;
+ mFrameRateCategoryView = null;
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
mHasIdledMessage = false;
} else {
@@ -12367,24 +12378,37 @@
|| (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
&& mPreferredFrameRate > 0)) {
frameRateCategory = FRAME_RATE_CATEGORY_HIGH;
+ if (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE) {
+ // We've received a velocity, so we'll let the velocity control the
+ // frame rate unless we receive additional motion events.
+ mIsTouchBoosting = false;
+ mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_VELOCITY;
+ mFrameRateCategoryView = null;
+ } else {
+ mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_UNKNOWN;
+ }
}
try {
if (mLastPreferredFrameRateCategory != frameRateCategory) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
- String reason = "none";
- switch (mFrameRateCategoryChangeReason) {
- case FRAME_RATE_CATEGORY_REASON_INTERMITTENT -> reason = "intermittent";
- case FRAME_RATE_CATEGORY_REASON_SMALL -> reason = "small";
- case FRAME_RATE_CATEGORY_REASON_LARGE -> reason = "large";
- case FRAME_RATE_CATEGORY_REASON_REQUESTED -> reason = "requested";
- case FRAME_RATE_CATEGORY_REASON_INVALID -> reason = "invalid frame rate";
- }
- String sourceView = mFrameRateCategoryView == null ? "No View Given"
+ String reason = reasonToString(mFrameRateCategoryChangeReason);
+ String sourceView = mFrameRateCategoryView == null ? "-"
: mFrameRateCategoryView;
+ if (preferredFrameRateCategory == FRAME_RATE_CATEGORY_HIGH_HINT) {
+ reason = "touch boost";
+ sourceView = "-";
+ } else if (categoryFromConflictedFrameRates == frameRateCategory
+ && frameRateCategory != preferredFrameRateCategory
+ && mIsFrameRateConflicted
+ ) {
+ reason = "conflict";
+ sourceView = "-";
+ }
+ String category = categoryToString(frameRateCategory);
Trace.traceBegin(
Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRateCategory "
- + frameRateCategory + ", reason " + reason + ", "
+ + category + ", reason " + reason + ", "
+ sourceView);
}
mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
@@ -12398,6 +12422,35 @@
}
}
+ private static String categoryToString(int frameRateCategory) {
+ String category;
+ switch (frameRateCategory) {
+ case FRAME_RATE_CATEGORY_NO_PREFERENCE -> category = "no preference";
+ case FRAME_RATE_CATEGORY_LOW -> category = "low";
+ case FRAME_RATE_CATEGORY_NORMAL -> category = "normal";
+ case FRAME_RATE_CATEGORY_HIGH_HINT -> category = "high hint";
+ case FRAME_RATE_CATEGORY_HIGH -> category = "high";
+ default -> category = "default";
+ }
+ return category;
+ }
+
+ private static String reasonToString(int reason) {
+ String str;
+ switch (reason) {
+ case FRAME_RATE_CATEGORY_REASON_INTERMITTENT -> str = "intermittent";
+ case FRAME_RATE_CATEGORY_REASON_SMALL -> str = "small";
+ case FRAME_RATE_CATEGORY_REASON_LARGE -> str = "large";
+ case FRAME_RATE_CATEGORY_REASON_REQUESTED -> str = "requested";
+ case FRAME_RATE_CATEGORY_REASON_INVALID -> str = "invalid frame rate";
+ case FRAME_RATE_CATEGORY_REASON_VELOCITY -> str = "velocity";
+ case FRAME_RATE_CATEGORY_REASON_IDLE -> str = "idle";
+ case FRAME_RATE_CATEGORY_REASON_UNKNOWN -> str = "unknown";
+ default -> str = String.valueOf(reason);
+ }
+ return str;
+ }
+
private void setPreferredFrameRate(float preferredFrameRate) {
if (!shouldSetFrameRate() || (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
&& preferredFrameRate > 0)) {
@@ -12417,17 +12470,6 @@
mFrameRateCompatibility).applyAsyncUnsafe();
mLastPreferredFrameRate = preferredFrameRate;
}
- if (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE && mIsTouchBoosting) {
- // We've received a velocity, so we'll let the velocity control the
- // frame rate unless we receive additional motion events.
- mIsTouchBoosting = false;
- if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
- Trace.instant(
- Trace.TRACE_TAG_VIEW,
- "ViewRootImpl#setFrameRate velocity used, no touch boost on next frame"
- );
- }
- }
} catch (Exception e) {
Log.e(mTag, "Unable to set frame rate", e);
} finally {
@@ -12468,7 +12510,7 @@
* @param frameRateCategory the preferred frame rate category of a View
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
- public void votePreferredFrameRateCategory(int frameRateCategory) {
+ public void votePreferredFrameRateCategory(int frameRateCategory, int reason, View view) {
if (frameRateCategory == FRAME_RATE_CATEGORY_HIGH) {
mFrameRateCategoryHighCount = FRAME_RATE_CATEGORY_COUNT;
} else if (frameRateCategory == FRAME_RATE_CATEGORY_HIGH_HINT) {
@@ -12479,6 +12521,7 @@
mFrameRateCategoryLowCount = FRAME_RATE_CATEGORY_COUNT;
}
+ int oldCategory = mPreferredFrameRateCategory;
if (mFrameRateCategoryHighCount > 0) {
mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH;
} else if (mFrameRateCategoryHighHintCount > 0) {
@@ -12490,6 +12533,13 @@
}
mHasInvalidation = true;
checkIdleness();
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)
+ && mPreferredFrameRateCategory != oldCategory
+ && mPreferredFrameRateCategory == frameRateCategory
+ ) {
+ mFrameRateCategoryChangeReason = reason;
+ mFrameRateCategoryView = view.getClass().getSimpleName();
+ }
}
/**
@@ -12617,12 +12667,10 @@
mWindowlessBackKeyCallback = callback;
}
- void recordCategory(int category, int reason, View view) {
+ void recordViewPercentage(float percentage) {
if (!Trace.isEnabled()) return;
- if (category > mPreferredFrameRateCategory) {
- mFrameRateCategoryChangeReason = reason;
- mFrameRateCategoryView = view.getClass().getSimpleName();
- }
+ // Record the largest view of percentage to the display size.
+ mLargestChildPercentage = Math.max(percentage, mLargestChildPercentage);
}
/**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 64846d0..c127a43 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1571,8 +1571,9 @@
/**
* Value applicable for the {@link #PROPERTY_COMPAT_ALLOW_SMALL_COVER_SCREEN} property to
* provide a signal to the system that an application or its specific activities explicitly
- * opt into being displayed on small foldable device cover screens that measure at least 1.5
- * inches for the shorter dimension and at least 2.4 inches for the longer dimension.
+ * opt into being displayed on small cover screens on flippable style foldable devices that
+ * measure at least 1.5 inches up to 2.2 inches for the shorter dimension and at least 2.4
+ * inches up to 3.4 inches for the longer dimension
*/
@CompatSmallScreenPolicy
@FlaggedApi(Flags.FLAG_COVER_DISPLAY_OPT_IN)
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index e215950..614df7c 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -142,7 +142,7 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)")
void attachAccessibilityOverlayToDisplay(int displayId, in SurfaceControl surfaceControl);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE)")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.STATUS_BAR_SERVICE,android.Manifest.permission.MANAGE_ACCESSIBILITY})")
oneway void notifyQuickSettingsTilesChanged(int userId, in List<ComponentName> tileComponentNames);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index e15baae..1d9eb71 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -3056,19 +3056,17 @@
}
@GuardedBy("mLock")
- private void handleFailedIdsLocked(ArrayList<AutofillId> failedIds) {
- if (failedIds != null && !failedIds.isEmpty()) {
- if (sVerbose) {
- Log.v(TAG, "autofill(): total failed views: " + failedIds);
- }
- try {
- mService.setAutofillFailure(mSessionId, failedIds, mContext.getUserId());
- } catch (RemoteException e) {
- // In theory, we could ignore this error since it's not a big deal, but
- // in reality, we rather crash the app anyways, as the failure could be
- // a consequence of something going wrong on the server side...
- throw e.rethrowFromSystemServer();
- }
+ private void handleFailedIdsLocked(@NonNull ArrayList<AutofillId> failedIds) {
+ if (!failedIds.isEmpty() && sVerbose) {
+ Log.v(TAG, "autofill(): total failed views: " + failedIds);
+ }
+ try {
+ mService.setAutofillFailure(mSessionId, failedIds, mContext.getUserId());
+ } catch (RemoteException e) {
+ // In theory, we could ignore this error since it's not a big deal, but
+ // in reality, we rather crash the app anyways, as the failure could be
+ // a consequence of something going wrong on the server side...
+ throw e.rethrowFromSystemServer();
}
}
@@ -3090,7 +3088,7 @@
final View[] views = client.autofillClientFindViewsByAutofillIdTraversal(
Helper.toArray(ids));
- ArrayList<AutofillId> failedIds = null;
+ ArrayList<AutofillId> failedIds = new ArrayList<>();
for (int i = 0; i < itemCount; i++) {
final AutofillId id = ids.get(i);
@@ -3101,9 +3099,6 @@
// the service; this is fine, but we need to update the view status in the
// server side so it can be triggered again.
Log.d(TAG, "autofill(): no View with id " + id);
- if (failedIds == null) {
- failedIds = new ArrayList<>();
- }
failedIds.add(id);
continue;
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 139ebc3..d40eeda 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2112,6 +2112,17 @@
}
}
+ boolean shouldDrawHighlightsOnTop = highContrastTextSmallTextRect()
+ && canvas.isHighContrastTextEnabled();
+
+ // If high contrast text is drawing background rectangles behind the text, those cover up
+ // the cursor and correction highlighter etc. So just draw the text first, then draw the
+ // others on top of the text. If high contrast text isn't enabled: draw text last, as usual.
+ if (shouldDrawHighlightsOnTop) {
+ drawLayout(canvas, layout, highlightPaths, highlightPaints, selectionHighlight,
+ selectionHighlightPaint, cursorOffsetVertical, shouldDrawHighlightsOnTop);
+ }
+
if (mCorrectionHighlighter != null) {
mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
}
@@ -2136,9 +2147,19 @@
mInsertModeController.onDraw(canvas);
}
+ if (!shouldDrawHighlightsOnTop) {
+ drawLayout(canvas, layout, highlightPaths, highlightPaints, selectionHighlight,
+ selectionHighlightPaint, cursorOffsetVertical, shouldDrawHighlightsOnTop);
+ }
+ }
+
+ private void drawLayout(Canvas canvas, Layout layout, List<Path> highlightPaths,
+ List<Paint> highlightPaints, Path selectionHighlight, Paint selectionHighlightPaint,
+ int cursorOffsetVertical, boolean shouldDrawHighlightsOnTop) {
if (mTextView.canHaveDisplayList() && canvas.isHardwareAccelerated()) {
drawHardwareAccelerated(canvas, layout, highlightPaths, highlightPaints,
- selectionHighlight, selectionHighlightPaint, cursorOffsetVertical);
+ selectionHighlight, selectionHighlightPaint, cursorOffsetVertical,
+ shouldDrawHighlightsOnTop);
} else {
layout.draw(canvas, highlightPaths, highlightPaints, selectionHighlight,
selectionHighlightPaint, cursorOffsetVertical);
@@ -2147,14 +2168,13 @@
private void drawHardwareAccelerated(Canvas canvas, Layout layout,
List<Path> highlightPaths, List<Paint> highlightPaints,
- Path selectionHighlight, Paint selectionHighlightPaint, int cursorOffsetVertical) {
+ Path selectionHighlight, Paint selectionHighlightPaint, int cursorOffsetVertical,
+ boolean shouldDrawHighlightsOnTop) {
final long lineRange = layout.getLineRangeForDraw(canvas);
int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
if (lastLine < 0) return;
- boolean shouldDrawHighlightsOnTop = highContrastTextSmallTextRect()
- && canvas.isHighContrastTextEnabled();
if (!shouldDrawHighlightsOnTop) {
layout.drawWithoutText(canvas, highlightPaths, highlightPaints, selectionHighlight,
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 1721462..0373539 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -15490,8 +15490,9 @@
return x;
}
+ /** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- int getLineAtCoordinate(float y) {
+ public int getLineAtCoordinate(float y) {
y -= getTotalPaddingTop();
// Clamp the position to inside of the view.
y = Math.max(0.0f, y);
diff --git a/core/java/android/window/InputTransferToken.java b/core/java/android/window/InputTransferToken.java
index e572853..c62eee4 100644
--- a/core/java/android/window/InputTransferToken.java
+++ b/core/java/android/window/InputTransferToken.java
@@ -18,7 +18,6 @@
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
-import android.os.Binder;
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
@@ -30,6 +29,8 @@
import com.android.window.flags.Flags;
+import libcore.util.NativeAllocationRegistry;
+
import java.util.Objects;
/**
@@ -51,28 +52,51 @@
*/
@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
public final class InputTransferToken implements Parcelable {
+ private static native long nativeCreate();
+ private static native long nativeCreate(IBinder token);
+ private static native void nativeWriteToParcel(long nativeObject, Parcel out);
+ private static native long nativeReadFromParcel(Parcel in);
+ private static native IBinder nativeGetBinderToken(long nativeObject);
+ private static native long nativeGetNativeInputTransferTokenFinalizer();
+ private static native boolean nativeEquals(long nativeObject1, long nativeObject2);
+
+ private static final NativeAllocationRegistry sRegistry =
+ NativeAllocationRegistry.createMalloced(InputTransferToken.class.getClassLoader(),
+ nativeGetNativeInputTransferTokenFinalizer());
+
/**
* @hide
*/
- @NonNull
- public final IBinder mToken;
+ public final long mNativeObject;
+
+ private InputTransferToken(long nativeObject) {
+ mNativeObject = nativeObject;
+ sRegistry.registerNativeAllocation(this, nativeObject);
+ }
/**
* @hide
*/
public InputTransferToken(@NonNull IBinder token) {
- mToken = token;
+ this(nativeCreate(token));
}
/**
* @hide
*/
public InputTransferToken() {
- mToken = new Binder();
+ this(nativeCreate());
+ }
+
+ /**
+ * @hide
+ */
+ public IBinder getToken() {
+ return nativeGetBinderToken(mNativeObject);
}
private InputTransferToken(Parcel in) {
- mToken = in.readStrongBinder();
+ this(nativeReadFromParcel(in));
}
/**
@@ -88,7 +112,7 @@
*/
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeStrongBinder(mToken);
+ nativeWriteToParcel(mNativeObject, dest);
}
public static final @NonNull Creator<InputTransferToken> CREATOR = new Creator<>() {
@@ -101,13 +125,12 @@
}
};
-
/**
* @hide
*/
@Override
public int hashCode() {
- return Objects.hash(mToken);
+ return Objects.hash(getToken());
}
/**
@@ -118,7 +141,8 @@
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
InputTransferToken other = (InputTransferToken) obj;
- return other.mToken == mToken;
+ if (other.mNativeObject == mNativeObject) return true;
+ return nativeEquals(mNativeObject, other.mNativeObject);
}
}
diff --git a/core/java/android/window/StartingWindowRemovalInfo.java b/core/java/android/window/StartingWindowRemovalInfo.java
index 6999e5b..50f5191 100644
--- a/core/java/android/window/StartingWindowRemovalInfo.java
+++ b/core/java/android/window/StartingWindowRemovalInfo.java
@@ -59,28 +59,35 @@
*/
public boolean playRevealAnimation;
- /** The mode is no need to defer removing the starting window for IME */
- public static final int DEFER_MODE_NONE = 0;
+ /** The mode is default defer removing the snapshot starting window. */
+ public static final int DEFER_MODE_DEFAULT = 0;
- /** The mode to defer removing the starting window until IME has drawn */
+ /** The mode to defer removing the snapshot starting window until IME has drawn. */
public static final int DEFER_MODE_NORMAL = 1;
- /** The mode to defer the starting window removal until IME drawn and finished the rotation */
+ /**
+ * The mode to defer the snapshot starting window removal until IME drawn and finished the
+ * rotation.
+ */
public static final int DEFER_MODE_ROTATION = 2;
+ /** The mode is no need to defer removing the snapshot starting window. */
+ public static final int DEFER_MODE_NONE = 3;
+
@IntDef(prefix = { "DEFER_MODE_" }, value = {
- DEFER_MODE_NONE,
+ DEFER_MODE_DEFAULT,
DEFER_MODE_NORMAL,
DEFER_MODE_ROTATION,
+ DEFER_MODE_NONE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DeferMode {}
/**
- * Whether need to defer removing the starting window for IME.
+ * Whether need to defer removing the snapshot starting window.
* @hide
*/
- public @DeferMode int deferRemoveForImeMode;
+ public @DeferMode int deferRemoveMode;
/**
* The rounded corner radius
@@ -116,7 +123,7 @@
windowAnimationLeash = source.readTypedObject(SurfaceControl.CREATOR);
mainFrame = source.readTypedObject(Rect.CREATOR);
playRevealAnimation = source.readBoolean();
- deferRemoveForImeMode = source.readInt();
+ deferRemoveMode = source.readInt();
roundedCornerRadius = source.readFloat();
windowlessSurface = source.readBoolean();
removeImmediately = source.readBoolean();
@@ -128,7 +135,7 @@
dest.writeTypedObject(windowAnimationLeash, flags);
dest.writeTypedObject(mainFrame, flags);
dest.writeBoolean(playRevealAnimation);
- dest.writeInt(deferRemoveForImeMode);
+ dest.writeInt(deferRemoveMode);
dest.writeFloat(roundedCornerRadius);
dest.writeBoolean(windowlessSurface);
dest.writeBoolean(removeImmediately);
@@ -140,7 +147,7 @@
+ " frame=" + mainFrame
+ " playRevealAnimation=" + playRevealAnimation
+ " roundedCornerRadius=" + roundedCornerRadius
- + " deferRemoveForImeMode=" + deferRemoveForImeMode
+ + " deferRemoveMode=" + deferRemoveMode
+ " windowlessSurface=" + windowlessSurface
+ " removeImmediately=" + removeImmediately + "}";
}
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index 7b8cdff..7e77f15 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -24,6 +24,7 @@
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.view.SurfaceControl;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -111,7 +112,8 @@
/**
* Creates a decor surface in the parent Task of the TaskFragment. The created decor surface
* will be provided in {@link TaskFragmentTransaction#TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED}
- * event callback.
+ * event callback. The decor surface can be used to draw the divider between TaskFragments or
+ * other decorations.
*/
public static final int OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE = 14;
@@ -135,6 +137,15 @@
*/
public static final int OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH = 17;
+ /**
+ * Sets whether the decor surface will be boosted. When not boosted, the decor surface is placed
+ * below any TaskFragments in untrusted mode or any activities with uid different from the
+ * TaskFragmentOrganizer uid and just above its owner TaskFragment; when boosted, the decor
+ * surface is placed above all the non-boosted windows in the Task, the content of these
+ * non-boosted windows will be hidden and inputs are disabled.
+ */
+ public static final int OP_TYPE_SET_DECOR_SURFACE_BOOSTED = 18;
+
@IntDef(prefix = { "OP_TYPE_" }, value = {
OP_TYPE_UNKNOWN,
OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -155,6 +166,7 @@
OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE,
OP_TYPE_SET_DIM_ON_TASK,
OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH,
+ OP_TYPE_SET_DECOR_SURFACE_BOOSTED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface OperationType {}
@@ -186,12 +198,18 @@
private final boolean mMoveToBottomIfClearWhenLaunch;
+ private final boolean mBooleanValue;
+
+ @Nullable
+ private final SurfaceControl.Transaction mSurfaceTransaction;
+
private TaskFragmentOperation(@OperationType int opType,
@Nullable TaskFragmentCreationParams taskFragmentCreationParams,
@Nullable IBinder activityToken, @Nullable Intent activityIntent,
@Nullable Bundle bundle, @Nullable IBinder secondaryFragmentToken,
@Nullable TaskFragmentAnimationParams animationParams,
- boolean isolatedNav, boolean dimOnTask, boolean moveToBottomIfClearWhenLaunch) {
+ boolean isolatedNav, boolean dimOnTask, boolean moveToBottomIfClearWhenLaunch,
+ boolean booleanValue, @Nullable SurfaceControl.Transaction surfaceTransaction) {
mOpType = opType;
mTaskFragmentCreationParams = taskFragmentCreationParams;
mActivityToken = activityToken;
@@ -202,6 +220,8 @@
mIsolatedNav = isolatedNav;
mDimOnTask = dimOnTask;
mMoveToBottomIfClearWhenLaunch = moveToBottomIfClearWhenLaunch;
+ mBooleanValue = booleanValue;
+ mSurfaceTransaction = surfaceTransaction;
}
private TaskFragmentOperation(Parcel in) {
@@ -215,6 +235,8 @@
mIsolatedNav = in.readBoolean();
mDimOnTask = in.readBoolean();
mMoveToBottomIfClearWhenLaunch = in.readBoolean();
+ mBooleanValue = in.readBoolean();
+ mSurfaceTransaction = in.readTypedObject(SurfaceControl.Transaction.CREATOR);
}
@Override
@@ -229,6 +251,8 @@
dest.writeBoolean(mIsolatedNav);
dest.writeBoolean(mDimOnTask);
dest.writeBoolean(mMoveToBottomIfClearWhenLaunch);
+ dest.writeBoolean(mBooleanValue);
+ dest.writeTypedObject(mSurfaceTransaction, flags);
}
@NonNull
@@ -324,6 +348,22 @@
return mMoveToBottomIfClearWhenLaunch;
}
+ /** Returns the boolean value for this operation. */
+ public boolean getBooleanValue() {
+ return mBooleanValue;
+ }
+
+ /**
+ * Returns {@link SurfaceControl.Transaction} associated with this operation. Currently, this is
+ * only used by {@link TaskFragmentOperation#OP_TYPE_SET_DECOR_SURFACE_BOOSTED} to specify a
+ * {@link SurfaceControl.Transaction} that should be applied together with the transaction to
+ * change the decor surface layers.
+ */
+ @Nullable
+ public SurfaceControl.Transaction getSurfaceTransaction() {
+ return mSurfaceTransaction;
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
@@ -349,6 +389,10 @@
sb.append(", isolatedNav=").append(mIsolatedNav);
sb.append(", dimOnTask=").append(mDimOnTask);
sb.append(", moveToBottomIfClearWhenLaunch=").append(mMoveToBottomIfClearWhenLaunch);
+ sb.append(", booleanValue=").append(mBooleanValue);
+ if (mSurfaceTransaction != null) {
+ sb.append(", surfaceTransaction=").append(mSurfaceTransaction);
+ }
sb.append('}');
return sb.toString();
@@ -358,7 +402,7 @@
public int hashCode() {
return Objects.hash(mOpType, mTaskFragmentCreationParams, mActivityToken, mActivityIntent,
mBundle, mSecondaryFragmentToken, mAnimationParams, mIsolatedNav, mDimOnTask,
- mMoveToBottomIfClearWhenLaunch);
+ mMoveToBottomIfClearWhenLaunch, mBooleanValue, mSurfaceTransaction);
}
@Override
@@ -376,7 +420,9 @@
&& Objects.equals(mAnimationParams, other.mAnimationParams)
&& mIsolatedNav == other.mIsolatedNav
&& mDimOnTask == other.mDimOnTask
- && mMoveToBottomIfClearWhenLaunch == other.mMoveToBottomIfClearWhenLaunch;
+ && mMoveToBottomIfClearWhenLaunch == other.mMoveToBottomIfClearWhenLaunch
+ && mBooleanValue == other.mBooleanValue
+ && Objects.equals(mSurfaceTransaction, other.mSurfaceTransaction);
}
@Override
@@ -414,6 +460,11 @@
private boolean mMoveToBottomIfClearWhenLaunch;
+ private boolean mBooleanValue;
+
+ @Nullable
+ private SurfaceControl.Transaction mSurfaceTransaction;
+
/**
* @param opType the {@link OperationType} of this {@link TaskFragmentOperation}.
*/
@@ -505,13 +556,37 @@
}
/**
+ * Sets the boolean value for this operation.
+ * TODO(b/327338038) migrate other boolean values to use shared mBooleanValue
+ */
+ @NonNull
+ public Builder setBooleanValue(boolean booleanValue) {
+ mBooleanValue = booleanValue;
+ return this;
+ }
+
+ /**
+ * Sets {@link SurfaceControl.Transaction} associated with this operation. Currently, this
+ * is only used by {@link TaskFragmentOperation#OP_TYPE_SET_DECOR_SURFACE_BOOSTED} to
+ * specify a {@link SurfaceControl.Transaction} that should be applied together with the
+ * transaction to change the decor surface layers.
+ */
+ @NonNull
+ public Builder setSurfaceTransaction(
+ @Nullable SurfaceControl.Transaction surfaceTransaction) {
+ mSurfaceTransaction = surfaceTransaction;
+ return this;
+ }
+
+ /**
* Constructs the {@link TaskFragmentOperation}.
*/
@NonNull
public TaskFragmentOperation build() {
return new TaskFragmentOperation(mOpType, mTaskFragmentCreationParams, mActivityToken,
mActivityIntent, mBundle, mSecondaryFragmentToken, mAnimationParams,
- mIsolatedNav, mDimOnTask, mMoveToBottomIfClearWhenLaunch);
+ mIsolatedNav, mDimOnTask, mMoveToBottomIfClearWhenLaunch, mBooleanValue,
+ mSurfaceTransaction);
}
}
}
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 63a2474..ed1d434 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -14,3 +14,10 @@
description: "Enables desktop windowing"
bug: "304778354"
}
+
+flag {
+ name: "enable_desktop_windowing_modals_policy"
+ namespace: "lse_desktop_experience"
+ description: "Enables policy for modals activities"
+ bug: "319492844"
+}
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index c769518..6bd273b 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -659,6 +659,7 @@
private boolean privateSpaceFlagsEnabled() {
return android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()
&& android.multiuser.Flags.enablePrivateSpaceIntentRedirection();
}
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index 40a437f..9dec102 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -129,7 +129,6 @@
private final int mDefaultWindowAnimationStyleResId;
private final boolean mDebug;
- private final boolean mGridLayoutRecentsEnabled;
private final boolean mLowRamRecentsEnabled;
public TransitionAnimation(Context context, boolean debug, String tag) {
@@ -166,7 +165,6 @@
mConfigShortAnimTime = context.getResources().getInteger(
com.android.internal.R.integer.config_shortAnimTime);
- mGridLayoutRecentsEnabled = SystemProperties.getBoolean("ro.recents.grid", false);
mLowRamRecentsEnabled = ActivityManager.isLowRamDeviceStatic();
final TypedArray windowStyle = mContext.getTheme().obtainStyledAttributes(
@@ -768,10 +766,8 @@
// We scale the width and clip to the top/left square
float scale =
thumbWidth / (appWidth - contentInsets.left - contentInsets.right);
- if (!mGridLayoutRecentsEnabled) {
- int unscaledThumbHeight = (int) (thumbHeight / scale);
- mTmpFromClipRect.bottom = mTmpFromClipRect.top + unscaledThumbHeight;
- }
+ int unscaledThumbHeight = (int) (thumbHeight / scale);
+ mTmpFromClipRect.bottom = mTmpFromClipRect.top + unscaledThumbHeight;
Animation scaleAnim = new ScaleAnimation(
scaleUp ? scale : 1, scaleUp ? 1 : scale,
@@ -887,12 +883,6 @@
toY = appRect.height() / 2 * (1 - 1 / scaleW) + appRect.top;
pivotX = mTmpRect.width() / 2;
pivotY = appRect.height() / 2 / scaleW;
- if (mGridLayoutRecentsEnabled) {
- // In the grid layout, the header is displayed above the thumbnail instead of
- // overlapping it.
- fromY -= thumbHeightI;
- toY -= thumbHeightI * scaleW;
- }
} else {
pivotX = 0;
pivotY = 0;
@@ -936,10 +926,7 @@
// This AnimationSet uses the Interpolators assigned above.
AnimationSet set = new AnimationSet(false);
set.addAnimation(scale);
- if (!mGridLayoutRecentsEnabled) {
- // In the grid layout, the header should be shown for the whole animation.
- set.addAnimation(alpha);
- }
+ set.addAnimation(alpha);
set.addAnimation(translate);
set.addAnimation(clipAnim);
a = set;
@@ -958,10 +945,7 @@
// This AnimationSet uses the Interpolators assigned above.
AnimationSet set = new AnimationSet(false);
set.addAnimation(scale);
- if (!mGridLayoutRecentsEnabled) {
- // In the grid layout, the header should be shown for the whole animation.
- set.addAnimation(alpha);
- }
+ set.addAnimation(alpha);
set.addAnimation(translate);
a = set;
@@ -1081,8 +1065,7 @@
* @return whether the transition should show the thumbnail being scaled down.
*/
private boolean shouldScaleDownThumbnailTransition(int orientation) {
- return mGridLayoutRecentsEnabled
- || orientation == Configuration.ORIENTATION_PORTRAIT;
+ return orientation == Configuration.ORIENTATION_PORTRAIT;
}
private static int updateToTranslucentAnimIfNeeded(int anim, @TransitionOldType int transit) {
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index a2efbd2..a22232a 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -376,6 +376,12 @@
void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop);
/**
+ * Set the split screen focus to the left / top app or the right / bottom app based on
+ * {@param leftOrTop}.
+ */
+ void setSplitscreenFocus(boolean leftOrTop);
+
+ /**
* Shows the media output switcher dialog.
*
* @param packageName of the session for which the output switcher is shown.
diff --git a/core/java/com/android/internal/widget/CachingIconView.java b/core/java/com/android/internal/widget/CachingIconView.java
index 8ddd4ff..d67a630 100644
--- a/core/java/com/android/internal/widget/CachingIconView.java
+++ b/core/java/com/android/internal/widget/CachingIconView.java
@@ -113,7 +113,7 @@
}
@Nullable
- private Drawable loadSizeRestrictedIcon(@Nullable Icon icon) {
+ Drawable loadSizeRestrictedIcon(@Nullable Icon icon) {
return LocalImageResolver.resolveImage(icon, getContext(), mMaxDrawableWidth,
mMaxDrawableHeight);
}
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 06835f0..6d5a96a 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -37,7 +37,6 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.Icon;
-import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.text.Spannable;
@@ -1216,7 +1215,7 @@
return new ConversationHeaderData(
conversationText,
new OneToOneConversationAvatarData(
- resolveAvatarImage(conversationIcon)));
+ resolveAvatarImageForOneToOne(conversationIcon)));
}
final List<List<Notification.MessagingStyle.Message>> groupMessages = new ArrayList<>();
@@ -1283,18 +1282,29 @@
return new ConversationHeaderData(
conversationText,
- new GroupConversationAvatarData(resolveAvatarImage(lastIcon),
- resolveAvatarImage(secondLastIcon)));
+ new GroupConversationAvatarData(resolveAvatarImageForFacePile(lastIcon),
+ resolveAvatarImageForFacePile(secondLastIcon)));
}
/**
- * {@link ImageResolver#loadImage(Uri)} accepts Uri to load images. However Conversation Avatars
- * are received as Icon. So, we can't make use of ImageResolver.
+ * One To One Conversation Avatars is loaded by CachingIconView(conversation icon view).
*/
@Nullable
- private Drawable resolveAvatarImage(Icon conversationIcon) {
+ private Drawable resolveAvatarImageForOneToOne(Icon conversationIcon) {
try {
- return LocalImageResolver.resolveImage(conversationIcon, getContext());
+ return mConversationIconView.loadSizeRestrictedIcon(conversationIcon);
+ } catch (Exception ex) {
+ return null;
+ }
+ }
+
+ /**
+ * Group Avatar drawables are loaded by Icon.
+ */
+ @Nullable
+ private Drawable resolveAvatarImageForFacePile(Icon conversationIcon) {
+ try {
+ return conversationIcon.loadDrawable(getContext());
} catch (Exception ex) {
return null;
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index a0dc94f..ac961ee 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -266,6 +266,7 @@
"fd_utils.cpp",
"android_hardware_input_InputWindowHandle.cpp",
"android_hardware_input_InputApplicationHandle.cpp",
+ "android_window_InputTransferToken.cpp",
"android_window_WindowInfosListener.cpp",
"android_window_ScreenCapture.cpp",
"jni_common.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index aa63f4f..9bbd191 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -221,6 +221,7 @@
extern int register_android_tracing_PerfettoDataSource(JNIEnv* env);
extern int register_android_tracing_PerfettoDataSourceInstance(JNIEnv* env);
extern int register_android_tracing_PerfettoProducer(JNIEnv* env);
+extern int register_android_window_InputTransferToken(JNIEnv* env);
// Namespace for Android Runtime flags applied during boot time.
static const char* RUNTIME_NATIVE_BOOT_NAMESPACE = "runtime_native_boot";
@@ -1678,6 +1679,7 @@
REG_JNI(register_android_tracing_PerfettoDataSource),
REG_JNI(register_android_tracing_PerfettoDataSourceInstance),
REG_JNI(register_android_tracing_PerfettoProducer),
+ REG_JNI(register_android_window_InputTransferToken),
};
/*
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 07cbaad..3a1e883 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -19,24 +19,23 @@
//#define LOG_NDEBUG 0
-#include <inttypes.h>
-
-#include <nativehelper/JNIHelp.h>
-
#include <android-base/stringprintf.h>
#include <android_runtime/AndroidRuntime.h>
+#include <input/InputConsumer.h>
#include <input/InputTransport.h>
+#include <inttypes.h>
#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
#include <utils/Looper.h>
+
#include <variant>
#include <vector>
+
#include "android_os_MessageQueue.h"
#include "android_view_InputChannel.h"
#include "android_view_KeyEvent.h"
#include "android_view_MotionEvent.h"
-
-#include <nativehelper/ScopedLocalRef.h>
-
#include "core_jni_helpers.h"
namespace android {
diff --git a/core/jni/android_window_InputTransferToken.cpp b/core/jni/android_window_InputTransferToken.cpp
new file mode 100644
index 0000000..60568e30
--- /dev/null
+++ b/core/jni/android_window_InputTransferToken.cpp
@@ -0,0 +1,138 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "InputTransferToken"
+
+#include <android_runtime/android_window_InputTransferToken.h>
+#include <gui/InputTransferToken.h>
+#include <nativehelper/JNIHelp.h>
+
+#include "android_os_Parcel.h"
+#include "android_util_Binder.h"
+#include "core_jni_helpers.h"
+
+namespace android {
+
+static struct {
+ jclass clazz;
+ jfieldID mNativeObject;
+ jmethodID ctor;
+} gInputTransferTokenClassInfo;
+
+static jlong nativeCreate(JNIEnv* env, jclass clazz) {
+ sp<InputTransferToken> inputTransferToken = sp<InputTransferToken>::make();
+ inputTransferToken->incStrong((void*)nativeCreate);
+ return reinterpret_cast<jlong>(inputTransferToken.get());
+}
+
+static jlong nativeCreateFromBinder(JNIEnv* env, jclass clazz, jobject tokenBinderObj) {
+ if (tokenBinderObj == nullptr) {
+ return 0;
+ }
+ sp<IBinder> token(ibinderForJavaObject(env, tokenBinderObj));
+ if (token == nullptr) {
+ return 0;
+ }
+ sp<InputTransferToken> inputTransferToken = sp<InputTransferToken>::make(token);
+ inputTransferToken->incStrong((void*)nativeCreate);
+ return reinterpret_cast<jlong>(inputTransferToken.get());
+}
+
+static void nativeWriteToParcel(JNIEnv* env, jclass clazz, jlong nativeObj, jobject parcelObj) {
+ InputTransferToken* inputTransferToken = reinterpret_cast<InputTransferToken*>(nativeObj);
+ Parcel* parcel = parcelForJavaObject(env, parcelObj);
+ inputTransferToken->writeToParcel(parcel);
+}
+
+static jlong nativeReadFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
+ sp<InputTransferToken> inputTransferToken = sp<InputTransferToken>::make();
+ Parcel* parcel = parcelForJavaObject(env, parcelObj);
+ inputTransferToken->readFromParcel(parcel);
+ inputTransferToken->incStrong((void*)nativeCreate);
+ return reinterpret_cast<jlong>(inputTransferToken.get());
+}
+
+static jobject nativeGetBinderToken(JNIEnv* env, jclass clazz, jlong nativeObj) {
+ sp<InputTransferToken> inputTransferToken = reinterpret_cast<InputTransferToken*>(nativeObj);
+ return javaObjectForIBinder(env, inputTransferToken->mToken);
+}
+
+InputTransferToken* android_window_InputTransferToken_getNativeInputTransferToken(
+ JNIEnv* env, jobject inputTransferTokenObj) {
+ if (inputTransferTokenObj != nullptr &&
+ env->IsInstanceOf(inputTransferTokenObj, gInputTransferTokenClassInfo.clazz)) {
+ return reinterpret_cast<InputTransferToken*>(
+ env->GetLongField(inputTransferTokenObj,
+ gInputTransferTokenClassInfo.mNativeObject));
+ } else {
+ return nullptr;
+ }
+}
+
+jobject android_window_InputTransferToken_getJavaInputTransferToken(
+ JNIEnv* env, const InputTransferToken* inputTransferToken) {
+ if (inputTransferToken == nullptr || env == nullptr) {
+ return nullptr;
+ }
+
+ inputTransferToken->incStrong((void*)nativeCreate);
+ return env->NewObject(gInputTransferTokenClassInfo.clazz, gInputTransferTokenClassInfo.ctor,
+ reinterpret_cast<jlong>(inputTransferToken));
+}
+
+static void release(InputTransferToken* inputTransferToken) {
+ inputTransferToken->decStrong((void*)nativeCreate);
+}
+
+static jlong nativeGetNativeInputTransferTokenFinalizer(JNIEnv* env, jclass clazz) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&release));
+}
+
+static bool nativeEquals(JNIEnv* env, jclass clazz, jlong inputTransferTokenObj1,
+ jlong inputTransferTokenObj2) {
+ sp<InputTransferToken> inputTransferToken1(
+ reinterpret_cast<InputTransferToken*>(inputTransferTokenObj1));
+ sp<InputTransferToken> inputTransferToken2(
+ reinterpret_cast<InputTransferToken*>(inputTransferTokenObj2));
+
+ return inputTransferToken1 == inputTransferToken2;
+}
+
+static const JNINativeMethod sInputTransferTokenMethods[] = {
+ // clang-format off
+ {"nativeCreate", "()J", (void*)nativeCreate},
+ {"nativeCreate", "(Landroid/os/IBinder;)J", (void*)nativeCreateFromBinder},
+ {"nativeWriteToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteToParcel},
+ {"nativeReadFromParcel", "(Landroid/os/Parcel;)J", (void*)nativeReadFromParcel},
+ {"nativeGetBinderToken", "(J)Landroid/os/IBinder;", (void*)nativeGetBinderToken},
+ {"nativeGetNativeInputTransferTokenFinalizer", "()J", (void*)nativeGetNativeInputTransferTokenFinalizer},
+ {"nativeEquals", "(JJ)Z", (void*) nativeEquals},
+ // clang-format on
+};
+
+int register_android_window_InputTransferToken(JNIEnv* env) {
+ int err = RegisterMethodsOrDie(env, "android/window/InputTransferToken",
+ sInputTransferTokenMethods, NELEM(sInputTransferTokenMethods));
+ jclass inputTransferTokenClass = FindClassOrDie(env, "android/window/InputTransferToken");
+ gInputTransferTokenClassInfo.clazz = MakeGlobalRefOrDie(env, inputTransferTokenClass);
+ gInputTransferTokenClassInfo.mNativeObject =
+ GetFieldIDOrDie(env, gInputTransferTokenClassInfo.clazz, "mNativeObject", "J");
+ gInputTransferTokenClassInfo.ctor =
+ GetMethodIDOrDie(env, gInputTransferTokenClassInfo.clazz, "<init>", "(J)V");
+ return err;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/core/jni/include/android_runtime/android_window_InputTransferToken.h b/core/jni/include/android_runtime/android_window_InputTransferToken.h
new file mode 100644
index 0000000..75dbe37
--- /dev/null
+++ b/core/jni/include/android_runtime/android_window_InputTransferToken.h
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+#ifndef _ANDROID_WINDOW_INPUTTRANSFERTOKEN_H
+#define _ANDROID_WINDOW_INPUTTRANSFERTOKEN_H
+
+#include <gui/InputTransferToken.h>
+#include <jni.h>
+
+namespace android {
+
+extern InputTransferToken* android_window_InputTransferToken_getNativeInputTransferToken(
+ JNIEnv* env, jobject inputTransferTokenObj);
+
+extern jobject android_window_InputTransferToken_getJavaInputTransferToken(
+ JNIEnv* env, const InputTransferToken* inputTransferToken);
+
+} // namespace android
+
+#endif // _ANDROID_WINDOW_INPUTTRANSFERTOKEN_H
diff --git a/core/res/Android.bp b/core/res/Android.bp
index e2e419f..a44e92c 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -156,11 +156,13 @@
generate_product_characteristics_rro: true,
flags_packages: [
+ "android.app.contextualsearch.flags-aconfig",
"android.content.pm.flags-aconfig",
"android.provider.flags-aconfig",
"camera_platform_flags",
"android.net.platform.flags-aconfig",
"com.android.window.flags.window-aconfig",
+ "android.permission.flags-aconfig",
],
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8ea742d..9673fc7 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3801,12 +3801,12 @@
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_LOCK"
android:protectionLevel="internal|role" />
- <!-- Allows an application to manage policy related to theft detection.
+ <!-- Allows an application to query the device stolen state.
@FlaggedApi("android.app.admin.flags.device_theft_api_enabled")
@hide
@SystemApi
-->
- <permission android:name="android.permission.MANAGE_DEVICE_POLICY_THEFT_DETECTION"
+ <permission android:name="android.permission.QUERY_DEVICE_STOLEN_STATE"
android:protectionLevel="internal|role" />
<!-- Allows an application to manage policy related to system apps.
@@ -6781,7 +6781,7 @@
@FlaggedApi("android.hardware.biometrics.custom_biometric_prompt")
-->
<permission android:name="android.permission.SET_BIOMETRIC_DIALOG_LOGO"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|privileged" />
<!-- Allows an application to control keyguard. Only allowed for system processes.
@hide -->
@@ -6920,7 +6920,8 @@
projection. This is intended for on device screen recording system app.
@FlaggedApi("android.permission.flags.sensitive_notification_app_protection") -->
<permission android:name="android.permission.RECORD_SENSITIVE_CONTENT"
- android:protectionLevel="signature"/>
+ android:protectionLevel="signature"
+ android:featureFlag="android.permission.flags.sensitive_notification_app_protection"/>
<!-- @SystemApi Allows an application to read install sessions
@hide This is not a third-party API (intended for system apps). -->
@@ -7207,6 +7208,13 @@
<permission android:name="android.permission.ACCESS_SMARTSPACE"
android:protectionLevel="signature|privileged|development" />
+ <!-- @SystemApi Allows an application to start a contextual search.
+ @FlaggedApi("android.app.contextualsearch.flags.enable_service")
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.ACCESS_CONTEXTUAL_SEARCH"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="android.app.contextualsearch.flags.enable_service"/>
+
<!-- @SystemApi Allows an application to manage the wallpaper effects
generation service.
@hide <p>Not for use by third-party applications.</p> -->
@@ -8149,6 +8157,14 @@
<permission android:name="android.permission.EMERGENCY_INSTALL_PACKAGES"
android:protectionLevel="signature|privileged"/>
+ <!-- Allows internal applications to restrict display modes
+ <p>Not for use by third-party applications.
+ <p>Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.RESTRICT_DISPLAY_MODES"
+ android:protectionLevel="signature" />
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f59c099..e3f1cb6 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3586,14 +3586,6 @@
<!-- The minimum number of visible recent tasks to be presented to the user through the
SystemUI. Can be -1 if there is no minimum limit. -->
- <integer name="config_minNumVisibleRecentTasks_grid">-1</integer>
-
- <!-- The maximum number of visible recent tasks to be presented to the user through the
- SystemUI. Can be -1 if there is no maximum limit. -->
- <integer name="config_maxNumVisibleRecentTasks_grid">9</integer>
-
- <!-- The minimum number of visible recent tasks to be presented to the user through the
- SystemUI. Can be -1 if there is no minimum limit. -->
<integer name="config_minNumVisibleRecentTasks_lowRam">-1</integer>
<!-- The maximum number of visible recent tasks to be presented to the user through the
@@ -6985,4 +6977,8 @@
<!-- The key containing the branching boolean for legacy Search. -->
<string name="config_defaultContextualSearchLegacyEnabled" translatable="false" />
+
+ <!-- Whether WM DisplayContent supports high performance transitions
+ (lower-end devices may want to disable) -->
+ <bool name="config_deviceSupportsHighPerfTransitions">true</bool>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c603fa7..9628d30 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -392,8 +392,6 @@
<java-symbol type="string" name="config_hdmiCecSetMenuLanguageActivity" />
<java-symbol type="integer" name="config_minNumVisibleRecentTasks_lowRam" />
<java-symbol type="integer" name="config_maxNumVisibleRecentTasks_lowRam" />
- <java-symbol type="integer" name="config_minNumVisibleRecentTasks_grid" />
- <java-symbol type="integer" name="config_maxNumVisibleRecentTasks_grid" />
<java-symbol type="integer" name="config_minNumVisibleRecentTasks" />
<java-symbol type="integer" name="config_maxNumVisibleRecentTasks" />
<java-symbol type="integer" name="config_activeTaskDurationHours" />
@@ -5376,4 +5374,7 @@
<java-symbol type="string" name="config_defaultContextualSearchLegacyEnabled" />
<java-symbol type="string" name="unarchival_session_app_label" />
+
+ <!-- Whether WM DisplayContent supports high performance transitions -->
+ <java-symbol type="bool" name="config_deviceSupportsHighPerfTransitions" />
</resources>
diff --git a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
index 1925588..33f37da 100644
--- a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
+++ b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
@@ -25,6 +25,7 @@
import android.net.Uri;
import android.os.Parcel;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -39,6 +40,7 @@
import java.lang.reflect.Field;
@RunWith(AndroidJUnit4.class)
+@Presubmit
@SmallTest
public class AutomaticZenRuleTest {
private static final String CLASS = "android.app.AutomaticZenRule";
diff --git a/core/tests/coretests/src/android/app/NotificationChannelGroupTest.java b/core/tests/coretests/src/android/app/NotificationChannelGroupTest.java
index 625c66a..046f5ac 100644
--- a/core/tests/coretests/src/android/app/NotificationChannelGroupTest.java
+++ b/core/tests/coretests/src/android/app/NotificationChannelGroupTest.java
@@ -20,7 +20,7 @@
import static junit.framework.TestCase.assertTrue;
import android.os.Parcel;
-import android.test.AndroidTestCase;
+import android.platform.test.annotations.Presubmit;
import android.text.TextUtils;
import androidx.test.filters.SmallTest;
@@ -35,6 +35,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@Presubmit
public class NotificationChannelGroupTest {
private final String CLASS = "android.app.NotificationChannelGroup";
diff --git a/core/tests/coretests/src/android/app/NotificationChannelTest.java b/core/tests/coretests/src/android/app/NotificationChannelTest.java
index 56ab034..18209b5 100644
--- a/core/tests/coretests/src/android/app/NotificationChannelTest.java
+++ b/core/tests/coretests/src/android/app/NotificationChannelTest.java
@@ -46,6 +46,7 @@
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.VibrationEffect;
+import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.MediaStore.Audio.AudioColumns;
import android.test.mock.MockContentResolver;
@@ -74,6 +75,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@Presubmit
public class NotificationChannelTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
diff --git a/core/tests/coretests/src/android/app/NotificationHistoryTest.java b/core/tests/coretests/src/android/app/NotificationHistoryTest.java
index bd493f4..c44c1eb 100644
--- a/core/tests/coretests/src/android/app/NotificationHistoryTest.java
+++ b/core/tests/coretests/src/android/app/NotificationHistoryTest.java
@@ -21,6 +21,7 @@
import android.app.NotificationHistory.HistoricalNotification;
import android.graphics.drawable.Icon;
import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -35,13 +36,19 @@
import java.util.Set;
@RunWith(AndroidJUnit4.class)
+@Presubmit
public class NotificationHistoryTest {
- private HistoricalNotification getHistoricalNotification(int index) {
+ private static HistoricalNotification getHistoricalNotification(int index) {
return getHistoricalNotification("package" + index, index);
}
- private HistoricalNotification getHistoricalNotification(String packageName, int index) {
+ private static HistoricalNotification getHistoricalNotification(String packageName, int index) {
+ return getHistoricalNotification(packageName, index, /* includeIcon= */ true);
+ }
+
+ private static HistoricalNotification getHistoricalNotification(String packageName, int index,
+ boolean includeIcon) {
String expectedChannelName = "channelName" + index;
String expectedChannelId = "channelId" + index;
int expectedUid = 1123456 + index;
@@ -65,7 +72,7 @@
.setPostedTimeMs(expectedPostTime)
.setTitle(expectedTitle)
.setText(expectedText)
- .setIcon(expectedIcon)
+ .setIcon(includeIcon ? expectedIcon : null)
.setConversationId(conversationId)
.build();
}
@@ -376,7 +383,8 @@
List<HistoricalNotification> expectedEntries = new ArrayList<>();
for (int i = 10; i >= 1; i--) {
- HistoricalNotification n = getHistoricalNotification(i);
+ HistoricalNotification n = getHistoricalNotification("packageName" + i,
+ i, /* includeIcon= */ false);
expectedEntries.add(n);
history.addNotificationToWrite(n);
}
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 5b0502d..5cc1ee4 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -85,6 +85,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemProperties;
+import android.platform.test.annotations.Presubmit;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
@@ -107,6 +108,7 @@
import libcore.junit.util.compat.CoreCompatChangeRule;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
@@ -117,6 +119,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@Presubmit
public class NotificationTest {
private Context mContext;
@@ -768,6 +771,7 @@
}
@Test
+ @Ignore // TODO: b/329389261 - Restore or delete
public void testColors_ensureColors_dayMode_producesValidPalette() {
Notification.Colors c = new Notification.Colors();
boolean colorized = false;
@@ -796,6 +800,7 @@
}
@Test
+ @Ignore // TODO: b/329389261 - Restore or delete
public void testColors_ensureColors_colorized_producesValidPalette_red() {
validateColorizedPaletteForColor(Color.RED);
}
@@ -1244,29 +1249,25 @@
}
@Test
- public void testBigPictureStyle_setExtras_pictureIconNull_pictureIconKeyNull() {
+ public void testBigPictureStyle_setExtras_pictureIconNull_noPictureIconKey() {
Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle();
bpStyle.bigPicture((Bitmap) null);
Bundle extras = new Bundle();
bpStyle.addExtras(extras);
- assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isTrue();
- final Parcelable pictureIcon = extras.getParcelable(EXTRA_PICTURE_ICON);
- assertThat(pictureIcon).isNull();
+ assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isFalse();
}
@Test
- public void testBigPictureStyle_setExtras_pictureIconNull_pictureKeyNull() {
+ public void testBigPictureStyle_setExtras_pictureIconNull_noPictureKey() {
Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle();
bpStyle.bigPicture((Bitmap) null);
Bundle extras = new Bundle();
bpStyle.addExtras(extras);
- assertThat(extras.containsKey(EXTRA_PICTURE)).isTrue();
- final Parcelable picture = extras.getParcelable(EXTRA_PICTURE);
- assertThat(picture).isNull();
+ assertThat(extras.containsKey(EXTRA_PICTURE)).isFalse();
}
@Test
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
index 2905a5a..e118c98d 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -31,7 +31,6 @@
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.test.AndroidTestCase;
import android.util.Log;
-import android.util.Printer;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -47,15 +46,12 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Vector;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Phaser;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -407,138 +403,4 @@
}
assertFalse(allowed);
}
-
- /** Dumpsys information about a single database. */
-
- /**
- * Collect and parse dumpsys output. This is not a full parser. It is only enough to support
- * the unit tests.
- */
- private static class Dumpsys {
- // Regular expressions for parsing the output. Reportedly, regular expressions are
- // expensive, so these are created only if a dumpsys object is created.
- private static final Object sLock = new Object();
- static Pattern mPool;
- static Pattern mConnection;
- static Pattern mEntry;
- static Pattern mSingleWord;
- static Pattern mNone;
-
- // The raw strings read from dumpsys. Once loaded, this list never changes.
- final ArrayList<String> mRaw = new ArrayList<>();
-
- // Parsed dumpsys. This contains only the bits that are being tested.
- static class Connection {
- ArrayList<String> mRecent = new ArrayList<>();
- ArrayList<String> mLong = new ArrayList<>();
- }
- static class Database {
- String mPath;
- ArrayList<Connection> mConnection = new ArrayList<>();
- }
- ArrayList<Database> mDatabase;
- ArrayList<String> mConcurrent;
-
- Dumpsys() {
- SQLiteDebug.dump(
- new Printer() { public void println(String x) { mRaw.add(x); } },
- new String[0]);
- parse();
- }
-
- /** Parse the raw text. Return true if no errors were detected. */
- boolean parse() {
- initialize();
-
- // Reset the parsed information. This method may be called repeatedly.
- mDatabase = new ArrayList<>();
- mConcurrent = new ArrayList<>();
-
- Database current = null;
- Connection connection = null;
- Matcher matcher;
- for (int i = 0; i < mRaw.size(); i++) {
- final String line = mRaw.get(i);
- matcher = mPool.matcher(line);
- if (matcher.lookingAt()) {
- current = new Database();
- mDatabase.add(current);
- current.mPath = matcher.group(1);
- continue;
- }
- matcher = mConnection.matcher(line);
- if (matcher.lookingAt()) {
- connection = new Connection();
- current.mConnection.add(connection);
- continue;
- }
-
- if (line.contains("Most recently executed operations")) {
- i += readTable(connection.mRecent, i, mEntry);
- continue;
- }
-
- if (line.contains("Operations exceeding 2000ms")) {
- i += readTable(connection.mLong, i, mEntry);
- continue;
- }
- if (line.contains("Concurrently opened database files")) {
- i += readTable(mConcurrent, i, mSingleWord);
- continue;
- }
- }
- return true;
- }
-
- /**
- * Read a series of lines following a header. Return the number of lines read. The input
- * line number is the number of the header.
- */
- private int readTable(List<String> s, int header, Pattern p) {
- // Special case: if the first line is "<none>" then there are no more lines to the
- // table.
- if (lookingAt(header+1, mNone)) return 1;
-
- int i;
- for (i = header + 1; i < mRaw.size() && lookingAt(i, p); i++) {
- s.add(mRaw.get(i).trim());
- }
- return i - header;
- }
-
- /** Return true if the n'th raw line matches the pattern. */
- boolean lookingAt(int n, Pattern p) {
- return p.matcher(mRaw.get(n)).lookingAt();
- }
-
- /** Compile the regular expressions the first time. */
- private static void initialize() {
- synchronized (sLock) {
- if (mPool != null) return;
- mPool = Pattern.compile("Connection pool for (\\S+):");
- mConnection = Pattern.compile("\\s+Connection #(\\d+):");
- mEntry = Pattern.compile("\\s+(\\d+): ");
- mSingleWord = Pattern.compile(" (\\S+)$");
- mNone = Pattern.compile("\\s+<none>$");
- }
- }
- }
-
- @Test
- public void testDumpsys() throws Exception {
- Dumpsys dumpsys = new Dumpsys();
-
- assertEquals(1, dumpsys.mDatabase.size());
- // Note: cannot test mConcurrent because that attribute is not hermitic with respect to
- // the tests.
-
- Dumpsys.Database db = dumpsys.mDatabase.get(0);
-
- // Work with normalized paths.
- String wantPath = mDatabaseFile.toPath().toRealPath().toString();
- String realPath = new File(db.mPath).toPath().toRealPath().toString();
- assertEquals(wantPath, realPath);
-
- assertEquals(1, db.mConnection.size());
- }
}
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 48ba526..fc1fbb5 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -120,6 +120,10 @@
final var statsToken = ImeTracker.Token.empty();
mImeConsumer.onWindowFocusGained(true);
mController.show(WindowInsets.Type.ime(), true /* fromIme */, statsToken);
+ // Called once through the show flow.
+ verify(mController).applyAnimation(
+ eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */,
+ eq(statsToken));
// set control and verify visibility is applied.
InsetsSourceControl control = new InsetsSourceControl(ID_IME,
@@ -127,11 +131,11 @@
mController.onControlsChanged(new InsetsSourceControl[] { control });
// IME show animation should be triggered when control becomes available.
verify(mController).applyAnimation(
- eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */,
- eq(statsToken));
+ eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(false) /* fromIme */,
+ and(not(eq(statsToken)), notNull()));
verify(mController, never()).applyAnimation(
- eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(true) /* fromIme */,
- eq(statsToken));
+ eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(false) /* fromIme */,
+ and(not(eq(statsToken)), notNull()));
});
}
@@ -159,6 +163,10 @@
final var statsToken = ImeTracker.Token.empty();
if (imeVisible) {
mController.show(WindowInsets.Type.ime(), true /* fromIme */, statsToken);
+ // Called once through the show flow.
+ verify(mController).applyAnimation(eq(WindowInsets.Type.ime()),
+ eq(true) /* show */, eq(true) /* fromIme */,
+ eq(false) /* skipAnim */, eq(statsToken));
}
// set control and verify visibility is applied.
@@ -184,13 +192,17 @@
if (!hasViewFocus) {
final var statsTokenNext = ImeTracker.Token.empty();
mController.show(WindowInsets.Type.ime(), true /* fromIme */, statsTokenNext);
+ // Called once through the show flow.
+ verify(mController).applyAnimation(eq(WindowInsets.Type.ime()),
+ eq(true) /* show */, eq(true) /* fromIme */,
+ eq(false) /* skipAnim */, eq(statsTokenNext));
mController.onControlsChanged(new InsetsSourceControl[]{ control });
// Verify IME show animation should be triggered when control becomes available and
// the animation will be skipped by getAndClearSkipAnimationOnce invoked.
verify(control).getAndClearSkipAnimationOnce();
verify(mController).applyAnimation(eq(WindowInsets.Type.ime()),
- eq(true) /* show */, eq(true) /* fromIme */,
- eq(false) /* skipAnim */, eq(statsTokenNext));
+ eq(true) /* show */, eq(false) /* fromIme */,
+ eq(true) /* skipAnim */, and(not(eq(statsToken)), notNull()));
}
});
}
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 2544fcb..652011b 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -679,20 +679,20 @@
}
sInstrumentation.runOnMainSync(() -> {
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW, 0, null);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL, 0, null);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT, 0, null);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
FRAME_RATE_CATEGORY_HIGH_HINT);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH, 0, null);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT, 0, null);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL, 0, null);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW, 0, null);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
});
}
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index b60b806f..a5c9624 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -23,6 +23,8 @@
import static android.view.inputmethod.Flags.initiationWithoutInputConnection;
import static android.view.stylus.HandwritingTestUtil.createView;
+import static com.android.text.flags.Flags.handwritingCursorPosition;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeFalse;
@@ -129,6 +131,7 @@
public void onTouchEvent_startHandwriting_when_stylusMoveOnce_withinHWArea() {
mTestView1.setText("hello");
when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
+ when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0);
mHandwritingInitiator.onInputConnectionCreated(mTestView1);
final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
@@ -148,9 +151,51 @@
// After IMM.startHandwriting is triggered, onTouchEvent should return true for ACTION_MOVE
// events so that the events are not dispatched to the view tree.
assertThat(onTouchEventResult2).isTrue();
- // Since the stylus down point was inside the TextView's bounds, the handwriting initiator
- // does not need to set the cursor position.
- verify(mTestView1, never()).setSelection(anyInt());
+ if (handwritingCursorPosition()) {
+ // Cursor is placed at the end of the text.
+ verify(mTestView1).setSelection(5);
+ } else {
+ // Since the stylus down point was inside the TextView's bounds, the handwriting
+ // initiator does not need to set the cursor position.
+ verify(mTestView1, never()).setSelection(anyInt());
+ }
+ }
+
+ @Test
+ public void onTouchEvent_startHandwriting_multipleParagraphs() {
+ // End of line 0 is offset 10, end of line 1 is offset 20, end of line 2 is offset 30, end
+ // of line 3 is offset 40.
+ mTestView1.setText("line 0 \nline 1 \nline 2 \nline 3 ");
+ mTestView1.layout(0, 0, 500, 500);
+ when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
+ when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(2);
+
+ mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+ final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
+ final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2;
+ MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
+ boolean onTouchEventResult1 = mHandwritingInitiator.onTouchEvent(stylusEvent1);
+
+ final int x2 = x1 + mHandwritingSlop * 2;
+ final int y2 = y1;
+
+ MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
+ boolean onTouchEventResult2 = mHandwritingInitiator.onTouchEvent(stylusEvent2);
+
+ // Stylus movement within HandwritingArea should trigger IMM.startHandwriting once.
+ verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
+ assertThat(onTouchEventResult1).isFalse();
+ // After IMM.startHandwriting is triggered, onTouchEvent should return true for ACTION_MOVE
+ // events so that the events are not dispatched to the view tree.
+ assertThat(onTouchEventResult2).isTrue();
+ if (handwritingCursorPosition()) {
+ // Cursor is placed at the end of the paragraph containing line 2.
+ verify(mTestView1).setSelection(30);
+ } else {
+ // Since the stylus down point was inside the TextView's bounds, the handwriting
+ // initiator does not need to set the cursor position.
+ verify(mTestView1, never()).setSelection(anyInt());
+ }
}
@Test
@@ -197,6 +242,7 @@
public void onTouchEvent_startHandwriting_when_stylusMove_withinExtendedHWArea() {
mTestView1.setText("hello");
when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
+ when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0);
if (!mInitiateWithoutConnection) {
mHandwritingInitiator.onInputConnectionCreated(mTestView1);
@@ -214,9 +260,14 @@
// Stylus movement within extended HandwritingArea should trigger IMM.startHandwriting once.
verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
- // Since the stylus down point was outside the TextView's bounds, the handwriting initiator
- // sets the cursor position.
- verify(mTestView1).setSelection(4);
+ if (handwritingCursorPosition()) {
+ // Cursor is placed at the end of the text.
+ verify(mTestView1).setSelection(5);
+ } else {
+ // Since the stylus down point was outside the TextView's bounds, the handwriting
+ // initiator sets the cursor position.
+ verify(mTestView1).setSelection(4);
+ }
}
@Test
@@ -246,6 +297,8 @@
public void onTouchEvent_startHandwriting_servedViewUpdate_stylusMoveInExtendedHWArea() {
mTestView1.setText("hello");
when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
+ when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0);
+
// The stylus down point is between mTestView1 and mTestView2, but it is within the
// extended handwriting area of both views. It is closer to mTestView1.
final int x1 = sHwArea1.right + HW_BOUNDS_OFFSETS_RIGHT_PX / 2;
@@ -278,9 +331,14 @@
// Handwriting is started for this view since the stylus down point is closest to this
// view.
verify(mHandwritingInitiator).startHandwriting(mTestView1);
- // Since the stylus down point was outside the TextView's bounds, the handwriting initiator
- // sets the cursor position.
- verify(mTestView1).setSelection(4);
+ if (handwritingCursorPosition()) {
+ // Cursor is placed at the end of the text.
+ verify(mTestView1).setSelection(5);
+ } else {
+ // Since the stylus down point was outside the TextView's bounds, the handwriting
+ // initiator sets the cursor position.
+ verify(mTestView1).setSelection(4);
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
index 43e6227..dbabcea 100644
--- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
@@ -641,6 +641,7 @@
public void shouldForwardToParent_telephony_privateProfile() throws Exception {
mSetFlagsRule.enableFlags(
android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES,
android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_INTENT_REDIRECTION);
sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME;
diff --git a/data/etc/core.protolog.pb b/data/etc/core.protolog.pb
index 0415e44..e1670be 100644
--- a/data/etc/core.protolog.pb
+++ b/data/etc/core.protolog.pb
Binary files differ
diff --git a/data/etc/enhanced-confirmation.xml b/data/etc/enhanced-confirmation.xml
index 3b1867c..973bcb5 100644
--- a/data/etc/enhanced-confirmation.xml
+++ b/data/etc/enhanced-confirmation.xml
@@ -24,33 +24,49 @@
<enhanced-confirmation-trusted-package
package="com.example.app"
sha256-cert-digest="E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0"/>
-
...
<enhanced-confirmation-trusted-installer
package="com.example.installer"
sha256-cert-digest="E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0"/>
-
...
-The "enhanced-confirmation-trusted-package" entry shown above indicates that "com.example.app"
-should be considered a "trusted package". A "trusted package" will be exempt from ECM restrictions.
+The <enhanced-confirmation-trusted-package> entry shown in the above example indicates that
+"com.example.app" should be considered a "trusted package". A "trusted package" will be exempt from
+ECM restrictions.
-The "enhanced-confirmation-trusted-installer" entry shown above indicates that
-"com.example.installer" should be considered a "trusted installer". A "trusted installer", and all
-packages that it installs, will be exempt from ECM restrictions. (There are some exceptions to this.
-For example, a trusted installer, at the time of installing an app, can opt the app back in to ECM
-restrictions by setting the app's package source to PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
-or PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE.)
+The <enhanced-confirmation-trusted-installer> entry shown in the above example indicates that
+"com.example.app" should be considered a "trusted installer". Apps installed by "trusted installers"
+will be exempt from ECM restrictions, with conditions explained in the next few paragraphs.
-In either case:
+If zero <enhanced-confirmation-trusted-installer> entries are declared, then *all* packages will be
+exempt from ECM restrictions, except apps meeting *all* of the following criteria:
+
+ A. The app is not pre-installed, and
+ B. The app has no matching <enhanced-confirmation-trusted-package> entries declared, and
+ C. The app is marked by its installer as coming from an untrustworthy package source.
+
+(For example, an installer may set an app's package source to
+PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE or PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE,
+which are considered untrustworthy.)
+
+If one or more <enhanced-confirmation-trusted-installer> entries are declared, then packages must,
+in order to be exempt from ECM, meet at least one of the following criteria:
+
+ A. Be installed by an installer with a matching <enhanced-confirmation-trusted-installer> entry
+ declared, and be marked as coming from an "trustworthy" package source by the installer, or
+ B. Be installed via a pre-installed installer, and be marked as coming from a "trustworthy"
+ package source by the installer, or
+ C. Have a matching <enhanced-confirmation-trusted-package> entry declared.
+
+For either type of XML element:
- The "package" XML attribute refers to the app's package name.
- The "sha256-cert-digest" XML attribute refers to the SHA-256 hash of an app signing certificate.
For any entry to successfully apply to a package, both XML attributes must be present, and must
match the package. That is, the package name must match the "package" attribute, and the app must be
-signed by the signing certificate identified by the "sha256-cert-digest" attribute..
+signed by the signing certificate identified by the "sha256-cert-digest" attribute.
-->
<config></config>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 0f12438..749f0e1 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -553,6 +553,8 @@
<permission name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"/>
<!-- Permission required for CTS test - CtsWearableSensingServiceTestCases -->
<permission name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE"/>
+ <!-- Permission required for CTS test - OnDeviceIntelligenceManagerTest -->
+ <permission name="android.permission.USE_ON_DEVICE_INTELLIGENCE" />
<!-- Permission required for CTS test - CtsTelephonyProviderTestCases -->
<permission name="android.permission.WRITE_APN_SETTINGS"/>
<!-- Permission required for GTS test - GtsStatsdHostTestCases -->
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 0231d3a..d410d5f 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2665,12 +2665,6 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/Task.java"
},
- "4446998544419008924": {
- "message": "Moving to RESUMED: %s (starting new instance) callers=%s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
- },
"4037728373502324767": {
"message": "resumeNextFocusableActivityWhenRootTaskIsEmpty: %s, go home",
"level": "DEBUG",
@@ -2767,12 +2761,6 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/TaskFragment.java"
},
- "2088177629189452176": {
- "message": "Activity config changed during resume: %s, new next: %s",
- "level": "INFO",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/TaskFragment.java"
- },
"-8483536760290526299": {
"message": "resumeTopActivity: Resumed %s",
"level": "DEBUG",
@@ -3433,6 +3421,24 @@
"group": "WM_DEBUG_WALLPAPER",
"at": "com\/android\/server\/wm\/WallpaperController.java"
},
+ "-6856158722649737204": {
+ "message": "Waiting for offset complete...",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
+ "-5966696477376431672": {
+ "message": "Offset complete!",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
+ "4198834090919802045": {
+ "message": "Timeout waiting for wallpaper to offset: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"-3477087868568520027": {
"message": "No longer animating wallpaper targets!",
"level": "VERBOSE",
@@ -3463,8 +3469,32 @@
"group": "WM_DEBUG_WALLPAPER",
"at": "com\/android\/server\/wm\/WallpaperController.java"
},
- "-2504764636812266719": {
- "message": "New wallpaper: target=%s prev=%s",
+ "7408402065665963407": {
+ "message": "Wallpaper at display %d - visibility: %b, keyguardLocked: %b",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
+ "-8598497865499265448": {
+ "message": "Wallpaper target=%s prev=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
+ "-5402010429724738603": {
+ "message": "Wallpaper should be visible but has not been drawn yet. mWallpaperDrawState=%d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
+ "4151327328872447804": {
+ "message": "New home screen wallpaper: %s, prev: %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
+ "6943105284590482059": {
+ "message": "New lock\/shared screen wallpaper: %s, prev: %s",
"level": "DEBUG",
"group": "WM_DEBUG_WALLPAPER",
"at": "com\/android\/server\/wm\/WallpaperController.java"
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
index fa35b63..98935e9 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
@@ -19,13 +19,14 @@
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN;
-import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE;
import static androidx.window.common.CommonFoldingFeature.parseListFromString;
import android.annotation.NonNull;
import android.content.Context;
+import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
+import android.hardware.devicestate.DeviceStateUtil;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseIntArray;
@@ -54,29 +55,27 @@
private static final boolean DEBUG = false;
/**
- * Emulated device state {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)} to
+ * Emulated device state
+ * {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)} to
* {@link CommonFoldingFeature.State} map.
*/
private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray();
/**
- * Emulated device state received via
- * {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)}.
- * "Emulated" states differ from "base" state in the sense that they may not correspond 1:1 with
- * physical device states. They represent the state of the device when various software
- * features and APIs are applied. The emulated states generally consist of all "base" states,
- * but may have additional states such as "concurrent" or "rear display". Concurrent mode for
- * example is activated via public API and can be active in both the "open" and "half folded"
- * device states.
+ * Device state received via
+ * {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)}.
+ * The identifier returned through {@link DeviceState#getIdentifier()} may not correspond 1:1
+ * with the physical state of the device. This could correspond to the system state of the
+ * device when various software features or overrides are applied. The emulated states generally
+ * consist of all "base" states, but may have additional states such as "concurrent" or
+ * "rear display". Concurrent mode for example is activated via public API and can be active in
+ * both the "open" and "half folded" device states.
*/
- private int mCurrentDeviceState = INVALID_DEVICE_STATE_IDENTIFIER;
+ private DeviceState mCurrentDeviceState = new DeviceState(
+ new DeviceState.Configuration.Builder(INVALID_DEVICE_STATE_IDENTIFIER,
+ "INVALID").build());
- /**
- * Base device state received via
- * {@link DeviceStateManager.DeviceStateCallback#onBaseStateChanged(int)}.
- * "Base" in this context means the "physical" state of the device.
- */
- private int mCurrentBaseDeviceState = INVALID_DEVICE_STATE_IDENTIFIER;
+ private List<DeviceState> mSupportedStates;
@NonNull
private final RawFoldingFeatureProducer mRawFoldSupplier;
@@ -85,22 +84,11 @@
private final DeviceStateCallback mDeviceStateCallback = new DeviceStateCallback() {
@Override
- public void onStateChanged(int state) {
+ public void onDeviceStateChanged(@NonNull DeviceState state) {
mCurrentDeviceState = state;
mRawFoldSupplier.getData(DeviceStateManagerFoldingFeatureProducer
.this::notifyFoldingFeatureChange);
}
-
- @Override
- public void onBaseStateChanged(int state) {
- mCurrentBaseDeviceState = state;
-
- if (mDeviceStateToPostureMap.get(mCurrentDeviceState)
- == COMMON_STATE_USE_BASE_STATE) {
- mRawFoldSupplier.getData(DeviceStateManagerFoldingFeatureProducer
- .this::notifyFoldingFeatureChange);
- }
- }
};
public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context,
@@ -109,6 +97,7 @@
mRawFoldSupplier = rawFoldSupplier;
String[] deviceStatePosturePairs = context.getResources()
.getStringArray(R.array.config_device_state_postures);
+ mSupportedStates = deviceStateManager.getSupportedDeviceStates();
boolean isHalfOpenedSupported = false;
for (String deviceStatePosturePair : deviceStatePosturePairs) {
String[] deviceStatePostureMapping = deviceStatePosturePair.split(":");
@@ -168,7 +157,7 @@
*/
private boolean isCurrentStateValid() {
// If the device state is not found in the map, indexOfKey returns a negative number.
- return mDeviceStateToPostureMap.indexOfKey(mCurrentDeviceState) >= 0;
+ return mDeviceStateToPostureMap.indexOfKey(mCurrentDeviceState.getIdentifier()) >= 0;
}
@Override
@@ -177,7 +166,9 @@
if (hasListeners()) {
mRawFoldSupplier.addDataChangedCallback(this::notifyFoldingFeatureChange);
} else {
- mCurrentDeviceState = INVALID_DEVICE_STATE_IDENTIFIER;
+ mCurrentDeviceState = new DeviceState(
+ new DeviceState.Configuration.Builder(INVALID_DEVICE_STATE_IDENTIFIER,
+ "INVALID").build());
mRawFoldSupplier.removeDataChangedCallback(this::notifyFoldingFeatureChange);
}
}
@@ -251,10 +242,13 @@
@CommonFoldingFeature.State
private int currentHingeState() {
@CommonFoldingFeature.State
- int posture = mDeviceStateToPostureMap.get(mCurrentDeviceState, COMMON_STATE_UNKNOWN);
+ int posture = mDeviceStateToPostureMap.get(mCurrentDeviceState.getIdentifier(),
+ COMMON_STATE_UNKNOWN);
if (posture == CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE) {
- posture = mDeviceStateToPostureMap.get(mCurrentBaseDeviceState, COMMON_STATE_UNKNOWN);
+ posture = mDeviceStateToPostureMap.get(
+ DeviceStateUtil.calculateBaseStateIdentifier(mCurrentDeviceState,
+ mSupportedStates), COMMON_STATE_UNKNOWN);
}
return posture;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index d31bf2a..a3d2d7f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -20,6 +20,7 @@
import android.app.Activity;
import android.content.Context;
+import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateRequest;
import android.hardware.display.DisplayManager;
@@ -40,6 +41,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -101,7 +103,9 @@
mDisplayManager = context.getSystemService(DisplayManager.class);
mExecutor = context.getMainExecutor();
- mCurrentSupportedDeviceStates = mDeviceStateManager.getSupportedStates();
+ // TODO(b/329436166): Update the usage of device state manager API's
+ mCurrentSupportedDeviceStates = getSupportedStateIdentifiers(
+ mDeviceStateManager.getSupportedDeviceStates());
mFoldedDeviceStates = context.getResources().getIntArray(
R.array.config_foldedDeviceStates);
@@ -446,9 +450,10 @@
}
@Override
- public void onSupportedStatesChanged(int[] supportedStates) {
+ public void onSupportedStatesChanged(@NonNull List<DeviceState> supportedStates) {
synchronized (mLock) {
- mCurrentSupportedDeviceStates = supportedStates;
+ // TODO(b/329436166): Update the usage of device state manager API's
+ mCurrentSupportedDeviceStates = getSupportedStateIdentifiers(supportedStates);
updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus());
updateRearDisplayPresentationStatusListeners(
getCurrentRearDisplayPresentationModeStatus());
@@ -456,9 +461,10 @@
}
@Override
- public void onStateChanged(int state) {
+ public void onDeviceStateChanged(@NonNull DeviceState state) {
synchronized (mLock) {
- mCurrentDeviceState = state;
+ // TODO(b/329436166): Update the usage of device state manager API's
+ mCurrentDeviceState = state.getIdentifier();
updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus());
updateRearDisplayPresentationStatusListeners(
getCurrentRearDisplayPresentationModeStatus());
@@ -482,6 +488,15 @@
return WindowAreaComponent.STATUS_AVAILABLE;
}
+ // TODO(b/329436166): Remove and update the usage of device state manager API's
+ private int[] getSupportedStateIdentifiers(@NonNull List<DeviceState> states) {
+ int[] identifiers = new int[states.size()];
+ for (int i = 0; i < states.size(); i++) {
+ identifiers[i] = states.get(i).getIdentifier();
+ }
+ return identifiers;
+ }
+
/**
* Helper method to determine if a rear display session is currently active by checking
* if the current device state is that which corresponds to {@code mRearDisplayState}.
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
index 8989fc5..f3d70f7 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -18,27 +18,32 @@
import android.content.Context
import android.content.Intent
+import android.content.pm.ShortcutInfo
+import android.content.res.Resources
import android.graphics.Color
import android.graphics.drawable.Icon
import android.os.UserHandle
import android.view.IWindowManager
import android.view.WindowManager
import android.view.WindowManagerGlobal
-import androidx.test.annotation.UiThreadTest
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.protolog.common.ProtoLog
import com.android.launcher3.icons.BubbleIconFactory
import com.android.wm.shell.R
+import com.android.wm.shell.animation.PhysicsAnimatorTestUtils
import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
+import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix
import com.android.wm.shell.common.FloatingContentCoordinator
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.taskview.TaskView
import com.android.wm.shell.taskview.TaskViewTaskController
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors.directExecutor
+import org.junit.After
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit
import java.util.function.Consumer
@@ -64,6 +69,7 @@
@Before
fun setUp() {
+ PhysicsAnimatorTestUtils.prepareForTest()
// Disable protolog tool when running the tests from studio
ProtoLog.REQUIRE_PROTOLOGTOOL = false
windowManager = WindowManagerGlobal.getWindowManagerService()!!
@@ -104,34 +110,158 @@
{ sysuiProxy },
shellExecutor
)
+
+ context
+ .getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
+ .edit()
+ .putBoolean(StackEducationView.PREF_STACK_EDUCATION, true)
+ .apply()
}
- @UiThreadTest
+ @After
+ fun tearDown() {
+ PhysicsAnimatorTestUtils.tearDown()
+ }
+
@Test
fun addBubble() {
val bubble = createAndInflateBubble()
- bubbleStackView.addBubble(bubble)
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.addBubble(bubble)
+ }
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
assertThat(bubbleStackView.bubbleCount).isEqualTo(1)
}
- @UiThreadTest
@Test
fun tapBubbleToExpand() {
val bubble = createAndInflateBubble()
- bubbleStackView.addBubble(bubble)
- assertThat(bubbleStackView.bubbleCount).isEqualTo(1)
- bubble.iconView!!.performClick()
- // we're checking the expanded state in BubbleData because that's the source of truth. This
- // will eventually propagate an update back to the stack view, but setting the entire
- // pipeline is outside the scope of a unit test.
- assertThat(bubbleData.isExpanded).isTrue()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.addBubble(bubble)
+ }
+
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ assertThat(bubbleStackView.bubbleCount).isEqualTo(1)
+ var lastUpdate: BubbleData.Update? = null
+ val semaphore = Semaphore(0)
+ val listener =
+ BubbleData.Listener { update ->
+ lastUpdate = update
+ semaphore.release()
+ }
+ bubbleData.setListener(listener)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubble.iconView!!.performClick()
+ // we're checking the expanded state in BubbleData because that's the source of truth.
+ // This will eventually propagate an update back to the stack view, but setting the
+ // entire pipeline is outside the scope of a unit test.
+ assertThat(bubbleData.isExpanded).isTrue()
+ }
+
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(lastUpdate).isNotNull()
+ assertThat(lastUpdate!!.expandedChanged).isTrue()
+ assertThat(lastUpdate!!.expanded).isTrue()
+ }
+
+ @Test
+ fun tapDifferentBubble_shouldReorder() {
+ val bubble1 = createAndInflateChatBubble(key = "bubble1")
+ val bubble2 = createAndInflateChatBubble(key = "bubble2")
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.addBubble(bubble1)
+ bubbleStackView.addBubble(bubble2)
+ }
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+
+ assertThat(bubbleStackView.bubbleCount).isEqualTo(2)
+ assertThat(bubbleData.bubbles).hasSize(2)
+ assertThat(bubbleData.selectedBubble).isEqualTo(bubble2)
+ assertThat(bubble2.iconView).isNotNull()
+
+ var lastUpdate: BubbleData.Update? = null
+ val semaphore = Semaphore(0)
+ val listener =
+ BubbleData.Listener { update ->
+ lastUpdate = update
+ semaphore.release()
+ }
+ bubbleData.setListener(listener)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubble2.iconView!!.performClick()
+ assertThat(bubbleData.isExpanded).isTrue()
+
+ bubbleStackView.setSelectedBubble(bubble2)
+ bubbleStackView.isExpanded = true
+ }
+
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(lastUpdate!!.expanded).isTrue()
+ assertThat(lastUpdate!!.bubbles.map { it.key })
+ .containsExactly("bubble2", "bubble1")
+ .inOrder()
+
+ // wait for idle to allow the animation to start
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ // wait for the expansion animation to complete before interacting with the bubbles
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
+ AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y)
+
+ // tap on bubble1 to select it
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubble1.iconView!!.performClick()
+ }
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(bubbleData.selectedBubble).isEqualTo(bubble1)
+
+ // tap on bubble1 again to collapse the stack
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ // we have to set the selected bubble in the stack view manually because we don't have a
+ // listener wired up.
+ bubbleStackView.setSelectedBubble(bubble1)
+ bubble1.iconView!!.performClick()
+ }
+
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(bubbleData.selectedBubble).isEqualTo(bubble1)
+ assertThat(bubbleData.isExpanded).isFalse()
+ assertThat(lastUpdate!!.orderChanged).isTrue()
+ assertThat(lastUpdate!!.bubbles.map { it.key })
+ .containsExactly("bubble1", "bubble2")
+ .inOrder()
+ }
+
+ private fun createAndInflateChatBubble(key: String): Bubble {
+ val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button)
+ val shortcutInfo = ShortcutInfo.Builder(context, "fakeId").setIcon(icon).build()
+ val bubble =
+ Bubble(
+ key,
+ shortcutInfo,
+ /* desiredHeight= */ 6,
+ Resources.ID_NULL,
+ "title",
+ /* taskId= */ 0,
+ "locus",
+ /* isDismissable= */ true,
+ directExecutor()
+ ) {}
+ inflateBubble(bubble)
+ return bubble
}
private fun createAndInflateBubble(): Bubble {
val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button)
val bubble = Bubble.createAppBubble(intent, UserHandle(1), icon, directExecutor())
+ inflateBubble(bubble)
+ return bubble
+ }
+
+ private fun inflateBubble(bubble: Bubble) {
bubble.setInflateSynchronously(true)
bubbleData.notificationEntryUpdated(bubble, true, false)
@@ -152,7 +282,6 @@
assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
assertThat(bubble.isInflated).isTrue()
- return bubble
}
private class FakeBubbleStackViewManager : BubbleStackViewManager {
@@ -176,7 +305,7 @@
r.run()
}
- override fun removeCallbacks(r: Runnable) {}
+ override fun removeCallbacks(r: Runnable?) {}
override fun hasCallback(r: Runnable): Boolean = false
}
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 7dd3961..00fb298 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -272,6 +272,10 @@
<dimen name="bubble_bar_expanded_view_corner_radius">16dp</dimen>
<!-- Corner radius for expanded view while it is being dragged -->
<dimen name="bubble_bar_expanded_view_corner_radius_dragged">28dp</dimen>
+ <!-- Width of the box around bottom center of the screen where drag only leads to dismiss -->
+ <dimen name="bubble_bar_dismiss_zone_width">192dp</dimen>
+ <!-- Height of the box around bottom center of the screen where drag only leads to dismiss -->
+ <dimen name="bubble_bar_dismiss_zone_height">242dp</dimen>
<!-- Bottom and end margin for compat buttons. -->
<dimen name="compat_button_margin">24dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
index ee8c414..b7f0890 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
@@ -505,7 +505,6 @@
// Check for a spring configuration. If one is present, we're either springing, or
// flinging-then-springing.
if (springConfig != null) {
-
// If there is no corresponding fling config, we're only springing.
if (flingConfig == null) {
// Apply the configuration and start the animation.
@@ -679,7 +678,6 @@
value: Float,
velocity: Float
) {
-
// If this property animation isn't relevant to this listener, ignore it.
if (!properties.contains(property)) {
return
@@ -702,7 +700,6 @@
finalVelocity: Float,
isFling: Boolean
): Boolean {
-
// If this property animation isn't relevant to this listener, ignore it.
if (!properties.contains(property)) {
return false
@@ -971,17 +968,18 @@
companion object {
/**
- * Constructor to use to for new physics animator instances in [getInstance]. This is
- * typically the default constructor, but [PhysicsAnimatorTestUtils] can change it so that
- * all code using the physics animator is given testable instances instead.
+ * Callback to notify that a new animator was created. Used in [PhysicsAnimatorTestUtils]
+ * to be able to keep track of animators and wait for them to finish.
*/
- internal var instanceConstructor: (Any) -> PhysicsAnimator<*> = ::PhysicsAnimator
+ internal var onAnimatorCreated: (PhysicsAnimator<*>, Any) -> Unit = { _, _ -> }
@JvmStatic
@Suppress("UNCHECKED_CAST")
fun <T : Any> getInstance(target: T): PhysicsAnimator<T> {
if (!animators.containsKey(target)) {
- animators[target] = instanceConstructor(target)
+ val animator = PhysicsAnimator(target)
+ onAnimatorCreated(animator, target)
+ animators[target] = animator
}
return animators[target] as PhysicsAnimator<T>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt
index 86eb8da..7defc26 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt
@@ -62,12 +62,9 @@
*/
@JvmStatic
fun prepareForTest() {
- val defaultConstructor = PhysicsAnimator.instanceConstructor
- PhysicsAnimator.instanceConstructor = fun(target: Any): PhysicsAnimator<*> {
- val animator = defaultConstructor(target)
+ PhysicsAnimator.onAnimatorCreated = { animator, target ->
allAnimatedObjects.add(target)
animatorTestHelpers[animator] = AnimatorTestHelper(animator)
- return animator
}
timeoutMs = 2000
@@ -158,12 +155,12 @@
@Throws(InterruptedException::class)
@Suppress("UNCHECKED_CAST")
fun <T : Any> blockUntilAnimationsEnd(
- properties: FloatPropertyCompat<in T>
+ vararg properties: FloatPropertyCompat<in T>
) {
for (target in allAnimatedObjects) {
try {
blockUntilAnimationsEnd(
- PhysicsAnimator.getInstance(target) as PhysicsAnimator<T>, properties)
+ PhysicsAnimator.getInstance(target) as PhysicsAnimator<T>, *properties)
} catch (e: ClassCastException) {
// Keep checking the other objects for ones whose types match the provided
// properties.
@@ -267,10 +264,8 @@
// Loop through the updates from the testable animator.
for (update in framesForProperty) {
-
// Check whether this frame satisfies the current matcher.
if (curMatcher(update)) {
-
// If that was the last unsatisfied matcher, we're good here. 'Verify' all remaining
// frames and return without failing.
if (matchers.size == 0) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 23bdd08..6524c96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -449,17 +449,21 @@
@Override
public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target,
- @NonNull MagnetizedObject draggedObject) {
- if (draggedObject.getUnderlyingObject() instanceof View view) {
+ @NonNull MagnetizedObject<?> draggedObject) {
+ Object underlyingObject = draggedObject.getUnderlyingObject();
+ if (underlyingObject instanceof View) {
+ View view = (View) underlyingObject;
animateDismissBubble(view, true);
}
}
@Override
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
- @NonNull MagnetizedObject draggedObject,
+ @NonNull MagnetizedObject<?> draggedObject,
float velX, float velY, boolean wasFlungOut) {
- if (draggedObject.getUnderlyingObject() instanceof View view) {
+ Object underlyingObject = draggedObject.getUnderlyingObject();
+ if (underlyingObject instanceof View) {
+ View view = (View) underlyingObject;
animateDismissBubble(view, false);
if (wasFlungOut) {
@@ -474,7 +478,9 @@
@Override
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target,
@NonNull MagnetizedObject<?> draggedObject) {
- if (draggedObject.getUnderlyingObject() instanceof View view) {
+ Object underlyingObject = draggedObject.getUnderlyingObject();
+ if (underlyingObject instanceof View) {
+ View view = (View) underlyingObject;
mExpandedAnimationController.dismissDraggedOutBubble(
view /* bubble */,
mDismissView.getHeight() /* translationYBy */,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index 056598b..b5b8a81 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -17,8 +17,10 @@
package com.android.wm.shell.bubbles.bar
import android.annotation.SuppressLint
+import android.graphics.RectF
import android.view.MotionEvent
import android.view.View
+import com.android.wm.shell.R
import com.android.wm.shell.bubbles.BubblePositioner
import com.android.wm.shell.common.bubbles.BubbleBarLocation
import com.android.wm.shell.common.bubbles.DismissView
@@ -43,6 +45,8 @@
private val magnetizedExpandedView: MagnetizedObject<BubbleBarExpandedView> =
MagnetizedObject.magnetizeView(expandedView)
private val magnetizedDismissTarget: MagnetizedObject.MagneticTarget
+ private val dismissZoneHeight: Int
+ private val dismissZoneWidth: Int
init {
magnetizedExpandedView.magnetListener = MagnetListener()
@@ -74,6 +78,11 @@
}
return@setOnTouchListener dragMotionEventHandler.onTouch(view, event) || magnetConsumed
}
+
+ dismissZoneHeight =
+ dismissView.resources.getDimensionPixelSize(R.dimen.bubble_bar_dismiss_zone_height)
+ dismissZoneWidth =
+ dismissView.resources.getDimensionPixelSize(R.dimen.bubble_bar_dismiss_zone_width)
}
/** Listener to receive callback about dragging events */
@@ -97,12 +106,23 @@
private var isMoving = false
private var screenCenterX: Int = -1
private var isOnLeft = false
+ private val dismissZone = RectF()
override fun onDown(v: View, ev: MotionEvent): Boolean {
// While animating, don't allow new touch events
if (expandedView.isAnimating) return false
- screenCenterX = bubblePositioner.screenRect.centerX()
isOnLeft = bubblePositioner.isBubbleBarOnLeft
+
+ val screenRect = bubblePositioner.screenRect
+ screenCenterX = screenRect.centerX()
+ val screenBottom = screenRect.bottom
+
+ dismissZone.set(
+ screenCenterX - dismissZoneWidth / 2f,
+ (screenBottom - dismissZoneHeight).toFloat(),
+ screenCenterX + dismissZoneHeight / 2f,
+ screenBottom.toFloat()
+ )
return true
}
@@ -122,6 +142,11 @@
expandedView.translationY = expandedViewInitialTranslationY + dy
dismissView.show()
+ // Check if we are in the zone around dismiss view where drag can only lead to dismiss
+ if (dismissZone.contains(ev.rawX, ev.rawY)) {
+ return
+ }
+
if (isOnLeft && ev.rawX > screenCenterX) {
isOnLeft = false
dragListener.onLocationChanged(BubbleBarLocation.RIGHT)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
index 8b4ac1a..d17e862 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
@@ -107,7 +107,7 @@
DeviceStateManager.class);
if (deviceStateManager != null) {
deviceStateManager.registerCallback(mMainExecutor, state -> onDevicePostureChanged(
- mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN)));
+ mDeviceStateToPostureMap.get(state.getIdentifier(), DEVICE_POSTURE_UNKNOWN)));
}
}
diff --git a/core/java/android/app/ondeviceintelligence/Content.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
similarity index 60%
copy from core/java/android/app/ondeviceintelligence/Content.aidl
copy to libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
index 40f0ef9..6781d08 100644
--- a/core/java/android/app/ondeviceintelligence/Content.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2024, The Android Open Source Project
+/*
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -14,9 +14,11 @@
* limitations under the License.
*/
-package android.app.ondeviceintelligence;
+@file:JvmName("AppCompatUtils")
-/**
- * @hide
- */
-parcelable Content;
+package com.android.wm.shell.compatui
+
+import android.app.TaskInfo
+fun isSingleTopActivityTranslucent(task: TaskInfo) =
+ task.isTopActivityTransparent && task.numActivities == 1
+
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index fb0ed15..6a3c8d2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -138,9 +138,9 @@
@WindowConfiguration.WindowingMode int windowingMode, int captionHeight) {
final Region region = new Region();
int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
- ? 2 * layout.stableInsets().top
- : mContext.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height);
+ ? mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height)
+ : 2 * layout.stableInsets().top;
// A thin, short Rect at the top of the screen.
if (windowingMode == WINDOWING_MODE_FREEFORM) {
int fromFreeformWidth = mContext.getResources().getDimensionPixelSize(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index dd8c1a0..992e5ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -19,6 +19,7 @@
import android.app.ActivityManager.RunningTaskInfo
import android.app.ActivityOptions
import android.app.PendingIntent
+import android.app.TaskInfo
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -46,6 +47,7 @@
import android.window.WindowContainerTransaction
import androidx.annotation.BinderThread
import com.android.internal.policy.ScreenDecorationsUtils
+import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
@@ -63,6 +65,7 @@
import com.android.wm.shell.common.annotations.ShellMainThread
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
+import com.android.wm.shell.compatui.isSingleTopActivityTranslucent
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
import com.android.wm.shell.draganddrop.DragAndDropController
@@ -78,8 +81,11 @@
import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.KtProtoLog
+import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
+import com.android.wm.shell.windowdecor.extension.isFreeform
+import com.android.wm.shell.windowdecor.extension.isFullscreen
import java.io.PrintWriter
import java.util.concurrent.Executor
import java.util.function.Consumer
@@ -163,8 +169,11 @@
private fun onInit() {
KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
shellCommandHandler.addDumpCallback(this::dump, this)
- shellCommandHandler.addCommandCallback("desktopmode", desktopModeShellCommandHandler,
- this)
+ shellCommandHandler.addCommandCallback(
+ "desktopmode",
+ desktopModeShellCommandHandler,
+ this
+ )
shellController.addExternalInterface(
ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE,
{ createExternalInterface() },
@@ -268,9 +277,11 @@
// Split-screen case where there are two focused tasks, then we find the child
// task to move to desktop.
val splitFocusedTask =
- if (allFocusedTasks[0].taskId == allFocusedTasks[1].parentTaskId)
+ if (allFocusedTasks[0].taskId == allFocusedTasks[1].parentTaskId) {
allFocusedTasks[1]
- else allFocusedTasks[0]
+ } else {
+ allFocusedTasks[0]
+ }
moveToDesktop(splitFocusedTask)
}
1 -> {
@@ -309,8 +320,18 @@
) {
if (!DesktopModeStatus.canEnterDesktopMode(context)) {
KtProtoLog.w(
- WM_SHELL_DESKTOP_MODE, "DesktopTasksController: Cannot enter desktop, " +
- "display does not meet minimum size requirements")
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: Cannot enter desktop, " +
+ "display does not meet minimum size requirements"
+ )
+ return
+ }
+ if (Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)) {
+ KtProtoLog.w(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: Cannot enter desktop, " +
+ "translucent top activity found. This is likely a modal dialog."
+ )
return
}
KtProtoLog.v(
@@ -445,7 +466,11 @@
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
exitDesktopTaskTransitionHandler.startTransition(
- Transitions.TRANSIT_EXIT_DESKTOP_MODE, wct, position, mOnAnimationFinishedCallback)
+ Transitions.TRANSIT_EXIT_DESKTOP_MODE,
+ wct,
+ position,
+ mOnAnimationFinishedCallback
+ )
} else {
shellTaskOrganizer.applyTransaction(wct)
releaseVisualIndicator()
@@ -491,8 +516,12 @@
KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d not found", taskId)
return
}
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d taskDisplayId=%d",
- taskId, task.displayId)
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "moveToNextDisplay: taskId=%d taskDisplayId=%d",
+ taskId,
+ task.displayId
+ )
val displayIds = rootTaskDisplayAreaOrganizer.displayIds.sorted()
// Get the first display id that is higher than current task display id
@@ -514,8 +543,12 @@
* No-op if task is already on that display per [RunningTaskInfo.displayId].
*/
private fun moveToDisplay(task: RunningTaskInfo, displayId: Int) {
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToDisplay: taskId=%d displayId=%d",
- task.taskId, displayId)
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "moveToDisplay: taskId=%d displayId=%d",
+ task.taskId,
+ displayId
+ )
if (task.displayId == displayId) {
KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "moveToDisplay: task already on display")
@@ -589,7 +622,8 @@
// Center the task in screen bounds
outBounds.offset(
screenBounds.centerX() - outBounds.centerX(),
- screenBounds.centerY() - outBounds.centerY())
+ screenBounds.centerY() - outBounds.centerY()
+ )
}
private fun getSnapBounds(taskInfo: RunningTaskInfo, position: SnapPosition): Rect {
@@ -736,11 +770,13 @@
val result = triggerTask?.let { task ->
when {
// If display has tasks stashed, handle as stashed launch
- desktopModeTaskRepository.isStashed(task.displayId) -> handleStashedTaskLaunch(task)
+ task.isStashed -> handleStashedTaskLaunch(task)
+ // Check if the task has a top transparent activity
+ shouldLaunchAsModal(task) -> handleTransparentTaskLaunch(task)
// Check if fullscreen task should be updated
- task.windowingMode == WINDOWING_MODE_FULLSCREEN -> handleFullscreenTaskLaunch(task)
+ task.isFullscreen -> handleFullscreenTaskLaunch(task)
// Check if freeform task should be updated
- task.windowingMode == WINDOWING_MODE_FREEFORM -> handleFreeformTaskLaunch(task)
+ task.isFreeform -> handleFreeformTaskLaunch(task)
else -> {
null
}
@@ -773,6 +809,13 @@
.forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
}
+ private val TaskInfo.isStashed: Boolean
+ get() = desktopModeTaskRepository.isStashed(displayId)
+
+ private fun shouldLaunchAsModal(task: TaskInfo): Boolean {
+ return Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)
+ }
+
private fun handleFreeformTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch")
val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId)
@@ -820,6 +863,16 @@
return wct
}
+ // Always launch transparent tasks in fullscreen.
+ private fun handleTransparentTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
+ // Already fullscreen, no-op.
+ if (task.isFullscreen)
+ return null
+ return WindowContainerTransaction().also { wct ->
+ addMoveToFullscreenChanges(wct, task)
+ }
+ }
+
private fun addMoveToDesktopChanges(
wct: WindowContainerTransaction,
taskInfo: RunningTaskInfo
@@ -900,9 +953,12 @@
) {
val wct = WindowContainerTransaction()
addMoveToSplitChanges(wct, taskInfo)
- splitScreenController.requestEnterSplitSelect(taskInfo, wct,
+ splitScreenController.requestEnterSplitSelect(
+ taskInfo,
+ wct,
if (leftOrTop) SPLIT_POSITION_TOP_OR_LEFT else SPLIT_POSITION_BOTTOM_OR_RIGHT,
- taskInfo.configuration.windowConfiguration.bounds)
+ taskInfo.configuration.windowConfiguration.bounds
+ )
}
}
@@ -960,7 +1016,8 @@
}
/**
- * Perform checks required on drag end. Move to fullscreen if drag ends in status bar area.
+ * Perform checks required on drag end. If indicator indicates a windowing mode change, perform
+ * that change. Otherwise, ensure bounds are up to date.
*
* @param taskInfo the task being dragged.
* @param position position of surface when drag ends.
@@ -971,7 +1028,8 @@
taskInfo: RunningTaskInfo,
position: Point,
inputCoordinate: PointF,
- taskBounds: Rect
+ taskBounds: Rect,
+ validDragArea: Rect
) {
if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
return
@@ -994,10 +1052,21 @@
releaseVisualIndicator()
snapToHalfScreen(taskInfo, SnapPosition.RIGHT)
}
- DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR,
DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR -> {
+ // If task bounds are outside valid drag area, snap them inward and perform a
+ // transaction to set bounds.
+ if (DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(
+ taskBounds, validDragArea)) {
+ val wct = WindowContainerTransaction()
+ wct.setBounds(taskInfo.token, taskBounds)
+ transitions.startTransition(TRANSIT_CHANGE, wct, null)
+ }
releaseVisualIndicator()
}
+ DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
+ throw IllegalArgumentException("Should not be receiving TO_DESKTOP_INDICATOR for " +
+ "a freeform task.")
+ }
}
// A freeform drag-move ended, remove the indicator immediately.
releaseVisualIndicator()
@@ -1097,7 +1166,8 @@
pendingIntentLaunchFlags =
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
}
val wct = WindowContainerTransaction()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index ad40493..2b433e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -85,6 +85,9 @@
/** Called when requested to go to fullscreen from the current active split app. */
void goToFullscreenFromSplit();
+ /** Called when splitscreen focused app is changed. */
+ void setSplitscreenFocus(boolean leftOrTop);
+
/** Get a string representation of a stage type */
static String stageTypeToString(@StageType int stage) {
switch (stage) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 86c8f04..3e34c30 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -485,6 +485,12 @@
}
}
+ public void setSplitscreenFocus(boolean leftOrTop) {
+ if (mStageCoordinator.isSplitActive()) {
+ mStageCoordinator.grantFocusToPosition(leftOrTop);
+ }
+ }
+
/** Move the specified task to fullscreen, regardless of focus state. */
public void moveTaskToFullscreen(int taskId, int exitReason) {
mStageCoordinator.moveTaskToFullscreen(taskId, exitReason);
@@ -1146,6 +1152,12 @@
public void goToFullscreenFromSplit() {
mMainExecutor.execute(SplitScreenController.this::goToFullscreenFromSplit);
}
+
+ @Override
+ public void setSplitscreenFocus(boolean leftOrTop) {
+ mMainExecutor.execute(
+ () -> SplitScreenController.this.setSplitscreenFocus(leftOrTop));
+ }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 36368df9..41890df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1592,6 +1592,11 @@
}
}
+ protected void grantFocusToPosition(boolean leftOrTop) {
+ grantFocusToStage(mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
+ ? getMainStagePosition() : getSideStagePosition());
+ }
+
private void clearRequestIfPresented() {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "clearRequestIfPresented");
if (mSideStageListener.mVisible && mSideStageListener.mHasChildren
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 1ce87ef..4465aef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -18,6 +18,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NONE;
import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NORMAL;
import static android.window.StartingWindowRemovalInfo.DEFER_MODE_ROTATION;
@@ -270,21 +271,18 @@
@Override
public final boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
- if (immediately) {
+ if (immediately
+ // Show the latest content as soon as possible for unlocking to home.
+ || mActivityType == ACTIVITY_TYPE_HOME
+ || info.deferRemoveMode == DEFER_MODE_NONE) {
removeImmediately();
- } else {
- scheduleRemove(info.deferRemoveForImeMode);
- return false;
+ return true;
}
- return true;
+ scheduleRemove(info.deferRemoveMode);
+ return false;
}
void scheduleRemove(@StartingWindowRemovalInfo.DeferMode int deferRemoveForImeMode) {
- // Show the latest content as soon as possible for unlocking to home.
- if (mActivityType == ACTIVITY_TYPE_HOME) {
- removeImmediately();
- return;
- }
mRemoveExecutor.removeCallbacks(mScheduledRunnable);
final long delayRemovalTime;
switch (deferRemoveForImeMode) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
index 4ea71490..5b402a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
@@ -212,6 +212,7 @@
switch (mType) {
case TYPE_RECENTS_DURING_DESKTOP:
case TYPE_RECENTS_DURING_SPLIT:
+ case TYPE_RECENTS_DURING_KEYGUARD:
mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
break;
default:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
index 7a50814..564e716 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
@@ -31,42 +31,42 @@
companion object {
/** @see [com.android.internal.protolog.common.ProtoLog.d] */
fun d(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ProtoLog.isEnabled(group)) {
+ if (group.isLogToLogcat) {
Log.d(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.v] */
fun v(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ProtoLog.isEnabled(group)) {
+ if (group.isLogToLogcat) {
Log.v(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.i] */
fun i(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ProtoLog.isEnabled(group)) {
+ if (group.isLogToLogcat) {
Log.i(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.w] */
fun w(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ProtoLog.isEnabled(group)) {
+ if (group.isLogToLogcat) {
Log.w(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.e] */
fun e(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ProtoLog.isEnabled(group)) {
+ if (group.isLogToLogcat) {
Log.e(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.wtf] */
fun wtf(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ProtoLog.isEnabled(group)) {
+ if (group.isLogToLogcat) {
Log.wtf(group.tag, String.format(messageString, *args))
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index b2eeea7..c59a1b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -19,9 +19,11 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
+import android.graphics.Rect;
import android.os.Handler;
import android.util.SparseArray;
import android.view.Choreographer;
@@ -186,7 +188,7 @@
final FluidResizeTaskPositioner taskPositioner =
new FluidResizeTaskPositioner(mTaskOrganizer, mTransitions, windowDecoration,
- mDisplayController, 0 /* disallowedAreaForEndBoundsHeight */);
+ mDisplayController);
final CaptionTouchEventListener touchEventListener =
new CaptionTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
@@ -286,8 +288,15 @@
mDragPointerId = e.getPointerId(0);
}
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
- mDragPositioningCallback.onDragPositioningEnd(
+ final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
+ DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(newTaskBounds,
+ mWindowDecorByTaskId.get(mTaskId).calculateValidDragArea());
+ if (newTaskBounds != taskInfo.configuration.windowConfiguration.getBounds()) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(taskInfo.token, newTaskBounds);
+ mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
+ }
final boolean wasDragging = mIsDragging;
mIsDragging = false;
return wasDragging;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 91e9601..9a48922 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.windowdecor;
+import android.annotation.NonNull;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
@@ -87,6 +88,7 @@
}
@Override
+ @NonNull
Rect calculateValidDragArea() {
final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
R.dimen.caption_left_buttons_width);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 98ff0ee..6d2109c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -30,6 +30,7 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.compatui.AppCompatUtils.isSingleTopActivityTranslucent;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
@@ -75,6 +76,7 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -639,7 +641,7 @@
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
mDesktopTasksController.onDragPositioningEnd(taskInfo, position,
new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
- newTaskBounds);
+ newTaskBounds, decoration.calculateValidDragArea());
if (touchingButton && !mHasLongClicked) {
// We need the input event to not be consumed here to end the ripple
// effect on the touched button. We will reset drag state in the ensuing
@@ -867,8 +869,9 @@
}
if (mTransitionDragActive) {
// Do not create an indicator at all if we're not past transition height.
- if (ev.getRawY() < mContext.getResources().getDimensionPixelSize(com.android
- .wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height)
+ DisplayLayout layout = mDisplayController
+ .getDisplayLayout(relevantDecor.mTaskInfo.displayId);
+ if (ev.getRawY() < 2 * layout.stableInsets().top
&& mMoveToDesktopAnimator == null) {
return;
}
@@ -1054,6 +1057,10 @@
&& taskInfo.isFocused) {
return false;
}
+ if (Flags.enableDesktopWindowingModalsPolicy()
+ && isSingleTopActivityTranslucent(taskInfo)) {
+ return false;
+ }
return DesktopModeStatus.isEnabled()
&& taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED
&& taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
@@ -1086,18 +1093,16 @@
windowDecoration.createResizeVeil();
final DragPositioningCallback dragPositioningCallback;
- final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
- R.dimen.desktop_mode_fullscreen_from_desktop_height);
if (!DesktopModeStatus.isVeiledResizeEnabled()) {
dragPositioningCallback = new FluidResizeTaskPositioner(
mTaskOrganizer, mTransitions, windowDecoration, mDisplayController,
- mDragStartListener, mTransactionFactory, transitionAreaHeight);
+ mDragStartListener, mTransactionFactory);
windowDecoration.setTaskDragResizer(
(FluidResizeTaskPositioner) dragPositioningCallback);
} else {
dragPositioningCallback = new VeiledResizeTaskPositioner(
mTaskOrganizer, windowDecoration, mDisplayController,
- mDragStartListener, mTransitions, transitionAreaHeight);
+ mDragStartListener, mTransitions);
windowDecoration.setTaskDragResizer(
(VeiledResizeTaskPositioner) dragPositioningCallback);
}
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 c9669a7..4c9e171 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
@@ -22,6 +22,7 @@
import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
@@ -499,6 +500,7 @@
* Determine valid drag area for this task based on elements in the app chip.
*/
@Override
+ @NonNull
Rect calculateValidDragArea() {
final int appTextWidth = ((DesktopModeAppControlsWindowDecorationViewHolder)
mWindowDecorViewHolder).getAppNameTextWidth();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index 5afbd54..82c399a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -131,7 +131,7 @@
t.setPosition(decoration.mTaskSurface, repositionTaskBounds.left, repositionTaskBounds.top);
}
- private static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
+ static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
PointF repositionStartPoint, float x, float y) {
final float deltaX = x - repositionStartPoint.x;
final float deltaY = y - repositionStartPoint.y;
@@ -140,49 +140,32 @@
}
/**
- * Calculates the new position of the top edge of the task and returns true if it is below the
- * disallowed area.
- *
- * @param disallowedAreaForEndBoundsHeight the height of the area that where the task positioner
- * should not finalize the bounds using WCT#setBounds
- * @param taskBoundsAtDragStart the bounds of the task on the first drag input event
- * @param repositionStartPoint initial input coordinate
- * @param y the y position of the motion event
- * @return true if the top of the task is below the disallowed area
+ * If task bounds are outside of provided drag area, snap the bounds to be just inside the
+ * drag area.
+ * @param repositionTaskBounds bounds determined by task positioner
+ * @param validDragArea the area that task must be positioned inside
+ * @return whether bounds were modified
*/
- static boolean isBelowDisallowedArea(int disallowedAreaForEndBoundsHeight,
- Rect taskBoundsAtDragStart, PointF repositionStartPoint, float y) {
- final float deltaY = y - repositionStartPoint.y;
- final float topPosition = taskBoundsAtDragStart.top + deltaY;
- return topPosition > disallowedAreaForEndBoundsHeight;
- }
-
- /**
- * Updates repositionTaskBounds to the final bounds of the task after the drag is finished. If
- * the bounds are outside of the valid drag area, the task is shifted back onto the edge of the
- * valid drag area.
- */
- static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
- PointF repositionStartPoint, float x, float y, Rect validDragArea) {
- updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint,
- x, y);
- snapTaskBoundsIfNecessary(repositionTaskBounds, validDragArea);
- }
-
- private static void snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) {
+ public static boolean snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) {
// If we were never supplied a valid drag area, do not restrict movement.
// Otherwise, we restrict deltas to keep task position inside the Rect.
- if (validDragArea.width() == 0) return;
+ if (validDragArea.width() == 0) return false;
+ boolean result = false;
if (repositionTaskBounds.left < validDragArea.left) {
repositionTaskBounds.offset(validDragArea.left - repositionTaskBounds.left, 0);
+ result = true;
} else if (repositionTaskBounds.left > validDragArea.right) {
repositionTaskBounds.offset(validDragArea.right - repositionTaskBounds.left, 0);
+ result = true;
}
if (repositionTaskBounds.top < validDragArea.top) {
repositionTaskBounds.offset(0, validDragArea.top - repositionTaskBounds.top);
+ result = true;
} else if (repositionTaskBounds.top > validDragArea.bottom) {
repositionTaskBounds.offset(0, validDragArea.bottom - repositionTaskBounds.top);
+ result = true;
}
+ return result;
}
private static float getMinWidth(DisplayController displayController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 6bfc7cd..6f8b3d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -60,9 +60,6 @@
private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mRepositionStartPoint = new PointF();
private final Rect mRepositionTaskBounds = new Rect();
- // If a task move (not resize) finishes with the positions y less than this value, do not
- // finalize the bounds there using WCT#setBounds
- private final int mDisallowedAreaForEndBoundsHeight;
private boolean mHasDragResized;
private boolean mIsResizingOrAnimatingResize;
private int mCtrlType;
@@ -70,11 +67,9 @@
@Surface.Rotation private int mRotation;
FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, Transitions transitions,
- WindowDecoration windowDecoration, DisplayController displayController,
- int disallowedAreaForEndBoundsHeight) {
+ WindowDecoration windowDecoration, DisplayController displayController) {
this(taskOrganizer, transitions, windowDecoration, displayController,
- dragStartListener -> {}, SurfaceControl.Transaction::new,
- disallowedAreaForEndBoundsHeight);
+ dragStartListener -> {}, SurfaceControl.Transaction::new);
}
FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
@@ -82,15 +77,13 @@
WindowDecoration windowDecoration,
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Supplier<SurfaceControl.Transaction> supplier,
- int disallowedAreaForEndBoundsHeight) {
+ Supplier<SurfaceControl.Transaction> supplier) {
mTaskOrganizer = taskOrganizer;
mTransitions = transitions;
mWindowDecoration = windowDecoration;
mDisplayController = displayController;
mDragStartListener = dragStartListener;
mTransactionSupplier = supplier;
- mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
}
@Override
@@ -157,14 +150,10 @@
wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
}
mDragResizeEndTransition = mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
- } else if (mCtrlType == CTRL_TYPE_UNDEFINED
- && DragPositioningCallbackUtility.isBelowDisallowedArea(
- mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
- y)) {
+ } else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
- mWindowDecoration.calculateValidDragArea());
+ DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
+ mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 5c69d55..c12a93e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -54,9 +54,6 @@
private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mRepositionStartPoint = new PointF();
private final Rect mRepositionTaskBounds = new Rect();
- // If a task move (not resize) finishes with the positions y less than this value, do not
- // finalize the bounds there using WCT#setBounds
- private final int mDisallowedAreaForEndBoundsHeight;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
private int mCtrlType;
private boolean mIsResizingOrAnimatingResize;
@@ -66,25 +63,22 @@
DesktopModeWindowDecoration windowDecoration,
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Transitions transitions,
- int disallowedAreaForEndBoundsHeight) {
+ Transitions transitions) {
this(taskOrganizer, windowDecoration, displayController, dragStartListener,
- SurfaceControl.Transaction::new, transitions, disallowedAreaForEndBoundsHeight);
+ SurfaceControl.Transaction::new, transitions);
}
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
DesktopModeWindowDecoration windowDecoration,
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Supplier<SurfaceControl.Transaction> supplier, Transitions transitions,
- int disallowedAreaForEndBoundsHeight) {
+ Supplier<SurfaceControl.Transaction> supplier, Transitions transitions) {
mDesktopWindowDecoration = windowDecoration;
mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
mDragStartListener = dragStartListener;
mTransactionSupplier = supplier;
mTransitions = transitions;
- mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
}
@Override
@@ -151,13 +145,10 @@
// won't be called.
resetVeilIfVisible();
}
- } else if (DragPositioningCallbackUtility.isBelowDisallowedArea(
- mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
- y)) {
+ } else {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
- mDesktopWindowDecoration.calculateValidDragArea());
+ DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
+ mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
index 7a64a47..a2293d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
@@ -17,6 +17,8 @@
package com.android.wm.shell.windowdecor.extension
import android.app.TaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS
import android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND
@@ -31,3 +33,9 @@
val appearance = taskDescription?.systemBarsAppearance ?: 0
return (appearance and APPEARANCE_LIGHT_CAPTION_BARS) != 0
}
+
+val TaskInfo.isFullscreen: Boolean
+ get() = windowingMode == WINDOWING_MODE_FULLSCREEN
+
+val TaskInfo.isFreeform: Boolean
+ get() = windowingMode == WINDOWING_MODE_FREEFORM
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
new file mode 100644
index 0000000..4cd2a36
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.compatui
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for {@link AppCompatUtils}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:AppCompatUtilsTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class AppCompatUtilsTest : ShellTestCase() {
+
+ @Test
+ fun testIsSingleTopActivityTranslucent() {
+ assertTrue(isSingleTopActivityTranslucent(
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ isTopActivityTransparent = true
+ numActivities = 1
+ }))
+ assertFalse(isSingleTopActivityTranslucent(
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ isTopActivityTransparent = true
+ numActivities = 0
+ }))
+ assertFalse(isSingleTopActivityTranslucent(
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ isTopActivityTransparent = false
+ numActivities = 1
+ }))
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
index 9703dce..bd39aa6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -68,17 +68,17 @@
)
var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top))
testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
WINDOWING_MODE_FREEFORM, CAPTION_HEIGHT)
assertThat(testRegion.bounds).isEqualTo(Rect(
DISPLAY_BOUNDS.width() / 2 - fromFreeformWidth / 2,
-50,
DISPLAY_BOUNDS.width() / 2 + fromFreeformWidth / 2,
- 2 * STABLE_INSETS.top))
+ transitionHeight))
testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
WINDOWING_MODE_MULTI_WINDOW, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top))
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 5df9dd3..254bf7d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -23,9 +23,14 @@
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.graphics.Point
+import android.graphics.PointF
+import android.graphics.Rect
import android.os.Binder
+import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
+import android.view.SurfaceControl
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_OPEN
@@ -40,14 +45,15 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.window.flags.Flags
import com.android.wm.shell.MockToken
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.transition.TestRemoteTransition
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.LaunchAdjacentController
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
@@ -65,16 +71,17 @@
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.OneShotRemoteHandler
+import com.android.wm.shell.transition.TestRemoteTransition
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS
import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_DESKTOP_MODE
import com.android.wm.shell.transition.Transitions.TransitionHandler
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.After
import org.junit.Assume.assumeTrue
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -86,15 +93,25 @@
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.kotlin.times
import org.mockito.Mockito.`when` as whenever
import org.mockito.quality.Strictness
+/**
+ * Test class for {@link DesktopTasksController}
+ *
+ * Usage: atest WMShellUnitTests:DesktopTasksControllerTest
+ */
@SmallTest
@RunWith(AndroidTestingRunner::class)
class DesktopTasksControllerTest : ShellTestCase() {
+ @JvmField
+ @Rule
+ val setFlagsRule = SetFlagsRule()
+
@Mock lateinit var testExecutor: ShellExecutor
@Mock lateinit var shellCommandHandler: ShellCommandHandler
@Mock lateinit var shellController: ShellController
@@ -109,7 +126,6 @@
ToggleResizeDesktopTaskTransitionHandler
@Mock lateinit var dragToDesktopTransitionHandler: DragToDesktopTransitionHandler
@Mock lateinit var launchAdjacentController: LaunchAdjacentController
- @Mock lateinit var desktopModeWindowDecoration: DesktopModeWindowDecoration
@Mock lateinit var splitScreenController: SplitScreenController
@Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler
@Mock lateinit var dragAndDropController: DragAndDropController
@@ -123,6 +139,7 @@
private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener
private val shellExecutor = TestShellExecutor()
+
// Mock running tasks are registered here so we can get the list from mock shell task organizer
private val runningTasks = mutableListOf<RunningTaskInfo>()
@@ -350,6 +367,18 @@
}
@Test
+ fun moveToDesktop_topActivityTranslucent_doesNothing() {
+ setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ val task = setUpFullscreenTask().apply {
+ isTopActivityTransparent = true
+ numActivities = 1
+ }
+
+ controller.moveToDesktop(task)
+ verifyWCTNotExecuted()
+ }
+
+ @Test
fun moveToDesktop_deviceNotSupported_deviceRestrictionsOverridden_taskIsMovedToDesktop() {
val task = setUpFullscreenTask()
@@ -424,7 +453,9 @@
val wct = getLatestMoveToDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FREEFORM)
- verify(splitScreenController).prepareExitSplitScreen(any(), anyInt(),
+ verify(splitScreenController).prepareExitSplitScreen(
+ any(),
+ anyInt(),
eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE)
)
}
@@ -436,7 +467,9 @@
val wct = getLatestMoveToDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FREEFORM)
- verify(splitScreenController, never()).prepareExitSplitScreen(any(), anyInt(),
+ verify(splitScreenController, never()).prepareExitSplitScreen(
+ any(),
+ anyInt(),
eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE)
)
}
@@ -744,6 +777,19 @@
}
@Test
+ fun handleRequest_shouldLaunchAsModal_returnSwitchToFullscreenWCT() {
+ setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ val task = setUpFreeformTask().apply {
+ isTopActivityTransparent = true
+ numActivities = 1
+ }
+
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ }
+
+ @Test
fun stashDesktopApps_stateUpdates() {
whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
@@ -822,7 +868,9 @@
val wct = getLatestMoveToDesktopWct()
assertThat(wct.changes[task4.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FREEFORM)
- verify(splitScreenController).prepareExitSplitScreen(any(), anyInt(),
+ verify(splitScreenController).prepareExitSplitScreen(
+ any(),
+ anyInt(),
eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE)
)
}
@@ -844,6 +892,31 @@
.isEqualTo(WINDOWING_MODE_FULLSCREEN)
}
+ @Test
+ fun onDesktopDragMove_endsOutsideValidDragArea_snapsToValidBounds() {
+ val task = setUpFreeformTask()
+ val mockSurface = mock(SurfaceControl::class.java)
+ val mockDisplayLayout = mock(DisplayLayout::class.java)
+ whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
+ controller.onDragPositioningMove(task, mockSurface, 200f,
+ Rect(100, -100, 500, 1000))
+
+ controller.onDragPositioningEnd(task,
+ Point(100, -100), /* position */
+ PointF(200f, -200f), /* inputCoordinate */
+ Rect(100, -100, 500, 1000), /* taskBounds */
+ Rect(0, 50, 2000, 2000) /* validDragArea */
+ )
+ val rectAfterEnd = Rect(100, 50, 500, 1150)
+ verify(transitions).startTransition(
+ eq(TRANSIT_CHANGE), Mockito.argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ change.configuration.windowConfiguration.bounds == rectAfterEnd
+ }
+ }, eq(null))
+ }
+
fun enterSplit_freeformTaskIsMovedToSplit() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
@@ -855,9 +928,12 @@
controller.enterSplit(DEFAULT_DISPLAY, false)
- verify(splitScreenController).requestEnterSplitSelect(task2, any(),
+ verify(splitScreenController).requestEnterSplitSelect(
+ task2,
+ any(),
SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT,
- task2.configuration.windowConfiguration.bounds)
+ task2.configuration.windowConfiguration.bounds
+ )
}
private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 6940739..8e9619d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -44,8 +44,8 @@
import android.view.WindowInsets.Type.navigationBars
import android.view.WindowInsets.Type.statusBars
import androidx.test.filters.SmallTest
-import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -65,11 +65,14 @@
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
+import java.util.Optional
+import java.util.function.Supplier
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
@@ -78,15 +81,14 @@
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
+import org.mockito.kotlin.spy
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
-import java.util.Optional
-import java.util.function.Supplier
-import org.mockito.Mockito
-import org.mockito.kotlin.spy
-
-/** Tests of [DesktopModeWindowDecorViewModel] */
+/**
+ * Tests of [DesktopModeWindowDecorViewModel]
+ * Usage: atest WMShellUnitTests:DesktopModeWindowDecorViewModelTests
+ */
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
@@ -291,6 +293,19 @@
}
@Test
+ fun testDescorationIsNotCreatedForTopTranslucentActivities() {
+ setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply {
+ isTopActivityTransparent = true
+ numActivities = 1
+ }
+ onTaskOpening(task)
+
+ verify(mockDesktopModeWindowDecorFactory, never())
+ .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ }
+
+ @Test
fun testRelayoutRunsWhenStatusBarsInsetsSourceVisibilityChanges() {
val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
val decoration = setUpMockDecorationForTask(task)
@@ -470,7 +485,8 @@
private fun setUpMockDecorationForTask(task: RunningTaskInfo): DesktopModeWindowDecoration {
val decoration = mock(DesktopModeWindowDecoration::class.java)
- whenever(mockDesktopModeWindowDecorFactory.create(
+ whenever(
+ mockDesktopModeWindowDecorFactory.create(
any(), any(), any(), eq(task), any(), any(), any(), any(), any())
).thenReturn(decoration)
decoration.mTaskInfo = task
@@ -489,7 +505,7 @@
"testEventReceiversOnMultipleDisplays",
/*width=*/ 400,
/*height=*/ 400,
- /*densityDpi=*/320,
+ /*densityDpi=*/ 320,
surfaceView.holder.surface,
DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
index e60be71..e6fabcf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -189,8 +189,9 @@
DISPLAY_BOUNDS.right - 100,
DISPLAY_BOUNDS.bottom - 100)
- DragPositioningCallbackUtility.onDragEnd(repositionTaskBounds, STARTING_BOUNDS,
- startingPoint, startingPoint.x - 1000, (DISPLAY_BOUNDS.bottom + 1000).toFloat(),
+ DragPositioningCallbackUtility.updateTaskBounds(repositionTaskBounds, STARTING_BOUNDS,
+ startingPoint, startingPoint.x - 1000, (DISPLAY_BOUNDS.bottom + 1000).toFloat())
+ DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(repositionTaskBounds,
validDragArea)
assertThat(repositionTaskBounds.left).isEqualTo(validDragArea.left)
assertThat(repositionTaskBounds.top).isEqualTo(validDragArea.bottom)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index de6903d..ce7b633 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -125,8 +125,7 @@
mockWindowDecoration,
mockDisplayController,
mockDragStartListener,
- mockTransactionFactory,
- DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT
+ mockTransactionFactory
)
}
@@ -576,31 +575,6 @@
})
}
- @Test
- fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.right.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = STARTING_BOUNDS.right.toFloat() + 5
- val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
-
- taskPositioner.onDragPositioningEnd(newX, newY)
-
- verify(mockTransitions, never()).startTransition(
- eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
- }}, eq(taskPositioner))
- }
-
private fun WindowContainerTransaction.Change.ofBounds(bounds: Rect): Boolean {
return ((windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) &&
bounds == configuration.windowConfiguration.bounds
@@ -656,70 +630,6 @@
}
@Test
- fun testDragResize_drag_taskPositionedInStableBounds() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.left.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = STARTING_BOUNDS.left.toFloat()
- val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
- verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
-
- taskPositioner.onDragPositioningEnd(
- newX,
- newY
- )
- // Verify task's top bound is set to stable bounds top since dragged outside stable bounds
- // but not in disallowed end bounds area.
- verify(mockTransitions).startTransition(
- eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds.top ==
- STABLE_BOUNDS_LANDSCAPE.top
- }}, eq(taskPositioner))
- }
-
- @Test
- fun testDragResize_drag_taskPositionedInValidDragArea() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.left.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = VALID_DRAG_AREA.left - 500f
- val newY = VALID_DRAG_AREA.bottom + 500f
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
- verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
-
- taskPositioner.onDragPositioningEnd(
- newX,
- newY
- )
- verify(mockTransitions).startTransition(
- eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds.top ==
- VALID_DRAG_AREA.bottom &&
- change.configuration.windowConfiguration.bounds.left ==
- VALID_DRAG_AREA.left
- }}, eq(taskPositioner))
- }
-
- @Test
fun testDragResize_drag_updatesStableBoundsOnRotate() {
// Test landscape stable bounds
performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 86253f3..7f6e538 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -138,8 +138,7 @@
mockDisplayController,
mockDragStartListener,
mockTransactionFactory,
- mockTransitions,
- DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT
+ mockTransitions
)
}
@@ -355,68 +354,6 @@
}
@Test
- fun testDragResize_drag_taskPositionedInStableBounds() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.left.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = STARTING_BOUNDS.left.toFloat()
- val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
- verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
-
- taskPositioner.onDragPositioningEnd(
- newX,
- newY
- )
- // Verify task's top bound is set to stable bounds top since dragged outside stable bounds
- // but not in disallowed end bounds area.
- verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds.top ==
- STABLE_BOUNDS_LANDSCAPE.top }},
- eq(taskPositioner))
- }
-
- @Test
- fun testDragResize_drag_taskPositionedInValidDragArea() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.left.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = VALID_DRAG_AREA.left - 500f
- val newY = VALID_DRAG_AREA.bottom + 500f
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
- verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
-
- taskPositioner.onDragPositioningEnd(
- newX,
- newY
- )
- verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds.top ==
- VALID_DRAG_AREA.bottom &&
- change.configuration.windowConfiguration.bounds.left ==
- VALID_DRAG_AREA.left }},
- eq(taskPositioner))
- }
-
- @Test
fun testDragResize_drag_updatesStableBoundsOnRotate() {
// Test landscape stable bounds
performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 4486f55..33830f1 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -563,6 +563,7 @@
"FrameInfoVisualizer.cpp",
"FrameMetricsReporter.cpp",
"Gainmap.cpp",
+ "HWUIProperties.sysprop",
"Interpolator.cpp",
"JankTracker.cpp",
"LightingInfo.cpp",
@@ -628,7 +629,6 @@
"AutoBackendTextureRelease.cpp",
"DeferredLayerUpdater.cpp",
"HardwareBitmapUploader.cpp",
- "HWUIProperties.sysprop",
"Layer.cpp",
"LayerUpdateQueue.cpp",
"ProfileDataContainer.cpp",
diff --git a/libs/hwui/Mesh.cpp b/libs/hwui/Mesh.cpp
index 37a7d74..5ef7acd 100644
--- a/libs/hwui/Mesh.cpp
+++ b/libs/hwui/Mesh.cpp
@@ -21,6 +21,8 @@
#include "SafeMath.h"
+namespace android {
+
static size_t min_vcount_for_mode(SkMesh::Mode mode) {
switch (mode) {
case SkMesh::Mode::kTriangles:
@@ -28,6 +30,7 @@
case SkMesh::Mode::kTriangleStrip:
return 3;
}
+ return 1;
}
// Re-implementation of SkMesh::validate to validate user side that their mesh is valid.
@@ -36,29 +39,30 @@
if (!mMeshSpec) {
FAIL_MESH_VALIDATE("MeshSpecification is required.");
}
- if (mVertexBufferData.empty()) {
+ if (mBufferData->vertexData().empty()) {
FAIL_MESH_VALIDATE("VertexBuffer is required.");
}
- auto meshStride = mMeshSpec->stride();
- auto meshMode = SkMesh::Mode(mMode);
+ size_t vertexStride = mMeshSpec->stride();
+ size_t vertexCount = mBufferData->vertexCount();
+ size_t vertexOffset = mBufferData->vertexOffset();
SafeMath sm;
- size_t vsize = sm.mul(meshStride, mVertexCount);
- if (sm.add(vsize, mVertexOffset) > mVertexBufferData.size()) {
+ size_t vertexSize = sm.mul(vertexStride, vertexCount);
+ if (sm.add(vertexSize, vertexOffset) > mBufferData->vertexData().size()) {
FAIL_MESH_VALIDATE(
"The vertex buffer offset and vertex count reads beyond the end of the"
" vertex buffer.");
}
- if (mVertexOffset % meshStride != 0) {
+ if (vertexOffset % vertexStride != 0) {
FAIL_MESH_VALIDATE("The vertex offset (%zu) must be a multiple of the vertex stride (%zu).",
- mVertexOffset, meshStride);
+ vertexOffset, vertexStride);
}
if (size_t uniformSize = mMeshSpec->uniformSize()) {
- if (!mBuilder->fUniforms || mBuilder->fUniforms->size() < uniformSize) {
+ if (!mUniformBuilder.fUniforms || mUniformBuilder.fUniforms->size() < uniformSize) {
FAIL_MESH_VALIDATE("The uniform data is %zu bytes but must be at least %zu.",
- mBuilder->fUniforms->size(), uniformSize);
+ mUniformBuilder.fUniforms->size(), uniformSize);
}
}
@@ -69,29 +73,33 @@
case SkMesh::Mode::kTriangleStrip:
return "triangle-strip";
}
+ return "unknown";
};
- if (!mIndexBufferData.empty()) {
- if (mIndexCount < min_vcount_for_mode(meshMode)) {
+
+ size_t indexCount = mBufferData->indexCount();
+ size_t indexOffset = mBufferData->indexOffset();
+ if (!mBufferData->indexData().empty()) {
+ if (indexCount < min_vcount_for_mode(mMode)) {
FAIL_MESH_VALIDATE("%s mode requires at least %zu indices but index count is %zu.",
- modeToStr(meshMode), min_vcount_for_mode(meshMode), mIndexCount);
+ modeToStr(mMode), min_vcount_for_mode(mMode), indexCount);
}
- size_t isize = sm.mul(sizeof(uint16_t), mIndexCount);
- if (sm.add(isize, mIndexOffset) > mIndexBufferData.size()) {
+ size_t isize = sm.mul(sizeof(uint16_t), indexCount);
+ if (sm.add(isize, indexOffset) > mBufferData->indexData().size()) {
FAIL_MESH_VALIDATE(
"The index buffer offset and index count reads beyond the end of the"
" index buffer.");
}
// If we allow 32 bit indices then this should enforce 4 byte alignment in that case.
- if (!SkIsAlign2(mIndexOffset)) {
+ if (!SkIsAlign2(indexOffset)) {
FAIL_MESH_VALIDATE("The index offset must be a multiple of 2.");
}
} else {
- if (mVertexCount < min_vcount_for_mode(meshMode)) {
+ if (vertexCount < min_vcount_for_mode(mMode)) {
FAIL_MESH_VALIDATE("%s mode requires at least %zu vertices but vertex count is %zu.",
- modeToStr(meshMode), min_vcount_for_mode(meshMode), mVertexCount);
+ modeToStr(mMode), min_vcount_for_mode(mMode), vertexCount);
}
- LOG_ALWAYS_FATAL_IF(mIndexCount != 0);
- LOG_ALWAYS_FATAL_IF(mIndexOffset != 0);
+ LOG_ALWAYS_FATAL_IF(indexCount != 0);
+ LOG_ALWAYS_FATAL_IF(indexOffset != 0);
}
if (!sm.ok()) {
@@ -100,3 +108,5 @@
#undef FAIL_MESH_VALIDATE
return {true, {}};
}
+
+} // namespace android
diff --git a/libs/hwui/Mesh.h b/libs/hwui/Mesh.h
index 69fda34..8c6ca97 100644
--- a/libs/hwui/Mesh.h
+++ b/libs/hwui/Mesh.h
@@ -25,6 +25,8 @@
#include <utility>
+namespace android {
+
class MeshUniformBuilder {
public:
struct MeshUniform {
@@ -103,32 +105,146 @@
sk_sp<SkMeshSpecification> fMeshSpec;
};
-class Mesh {
+// Storage for CPU and GPU copies of the vertex and index data of a mesh.
+class MeshBufferData {
public:
- Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode,
- std::vector<uint8_t>&& vertexBufferData, jint vertexCount, jint vertexOffset,
- std::unique_ptr<MeshUniformBuilder> builder, const SkRect& bounds)
- : mMeshSpec(meshSpec)
- , mMode(mode)
- , mVertexBufferData(std::move(vertexBufferData))
- , mVertexCount(vertexCount)
+ MeshBufferData(std::vector<uint8_t> vertexData, int32_t vertexCount, int32_t vertexOffset,
+ std::vector<uint8_t> indexData, int32_t indexCount, int32_t indexOffset)
+ : mVertexCount(vertexCount)
, mVertexOffset(vertexOffset)
- , mBuilder(std::move(builder))
- , mBounds(bounds) {}
-
- Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode,
- std::vector<uint8_t>&& vertexBufferData, jint vertexCount, jint vertexOffset,
- std::vector<uint8_t>&& indexBuffer, jint indexCount, jint indexOffset,
- std::unique_ptr<MeshUniformBuilder> builder, const SkRect& bounds)
- : mMeshSpec(meshSpec)
- , mMode(mode)
- , mVertexBufferData(std::move(vertexBufferData))
- , mVertexCount(vertexCount)
- , mVertexOffset(vertexOffset)
- , mIndexBufferData(std::move(indexBuffer))
, mIndexCount(indexCount)
, mIndexOffset(indexOffset)
- , mBuilder(std::move(builder))
+ , mVertexData(std::move(vertexData))
+ , mIndexData(std::move(indexData)) {}
+
+ void updateBuffers(GrDirectContext* context) const {
+ GrDirectContext::DirectContextID currentId = context == nullptr
+ ? GrDirectContext::DirectContextID()
+ : context->directContextID();
+ if (currentId == mSkiaBuffers.fGenerationId && mSkiaBuffers.fVertexBuffer != nullptr) {
+ // Nothing to update since the Android API does not support partial updates yet.
+ return;
+ }
+
+ mSkiaBuffers.fVertexBuffer =
+#ifdef __ANDROID__
+ SkMeshes::MakeVertexBuffer(context, mVertexData.data(), mVertexData.size());
+#else
+ SkMeshes::MakeVertexBuffer(mVertexData.data(), mVertexData.size());
+#endif
+ if (mIndexCount != 0) {
+ mSkiaBuffers.fIndexBuffer =
+#ifdef __ANDROID__
+ SkMeshes::MakeIndexBuffer(context, mIndexData.data(), mIndexData.size());
+#else
+ SkMeshes::MakeIndexBuffer(mIndexData.data(), mIndexData.size());
+#endif
+ }
+ mSkiaBuffers.fGenerationId = currentId;
+ }
+
+ SkMesh::VertexBuffer* vertexBuffer() const { return mSkiaBuffers.fVertexBuffer.get(); }
+
+ sk_sp<SkMesh::VertexBuffer> refVertexBuffer() const { return mSkiaBuffers.fVertexBuffer; }
+ int32_t vertexCount() const { return mVertexCount; }
+ int32_t vertexOffset() const { return mVertexOffset; }
+
+ sk_sp<SkMesh::IndexBuffer> refIndexBuffer() const { return mSkiaBuffers.fIndexBuffer; }
+ int32_t indexCount() const { return mIndexCount; }
+ int32_t indexOffset() const { return mIndexOffset; }
+
+ const std::vector<uint8_t>& vertexData() const { return mVertexData; }
+ const std::vector<uint8_t>& indexData() const { return mIndexData; }
+
+private:
+ struct CachedSkiaBuffers {
+ sk_sp<SkMesh::VertexBuffer> fVertexBuffer;
+ sk_sp<SkMesh::IndexBuffer> fIndexBuffer;
+ GrDirectContext::DirectContextID fGenerationId = GrDirectContext::DirectContextID();
+ };
+
+ mutable CachedSkiaBuffers mSkiaBuffers;
+ int32_t mVertexCount = 0;
+ int32_t mVertexOffset = 0;
+ int32_t mIndexCount = 0;
+ int32_t mIndexOffset = 0;
+ std::vector<uint8_t> mVertexData;
+ std::vector<uint8_t> mIndexData;
+};
+
+class Mesh {
+public:
+ // A snapshot of the mesh for use by the render thread.
+ //
+ // After a snapshot is taken, future uniform changes to the original Mesh will not modify the
+ // uniforms returned by makeSkMesh.
+ class Snapshot {
+ public:
+ Snapshot() = delete;
+ Snapshot(const Snapshot&) = default;
+ Snapshot(Snapshot&&) = default;
+ Snapshot& operator=(const Snapshot&) = default;
+ Snapshot& operator=(Snapshot&&) = default;
+ ~Snapshot() = default;
+
+ const SkMesh& getSkMesh() const {
+ SkMesh::VertexBuffer* vertexBuffer = mBufferData->vertexBuffer();
+ LOG_FATAL_IF(vertexBuffer == nullptr,
+ "Attempt to obtain SkMesh when vertexBuffer has not been created, did you "
+ "forget to call MeshBufferData::updateBuffers with a GrDirectContext?");
+ if (vertexBuffer != mMesh.vertexBuffer()) mMesh = makeSkMesh();
+ return mMesh;
+ }
+
+ private:
+ friend class Mesh;
+
+ Snapshot(sk_sp<SkMeshSpecification> meshSpec, SkMesh::Mode mode,
+ std::shared_ptr<const MeshBufferData> bufferData, sk_sp<const SkData> uniforms,
+ const SkRect& bounds)
+ : mMeshSpec(std::move(meshSpec))
+ , mMode(mode)
+ , mBufferData(std::move(bufferData))
+ , mUniforms(std::move(uniforms))
+ , mBounds(bounds) {}
+
+ SkMesh makeSkMesh() const {
+ const MeshBufferData& d = *mBufferData;
+ if (d.indexCount() != 0) {
+ return SkMesh::MakeIndexed(mMeshSpec, mMode, d.refVertexBuffer(), d.vertexCount(),
+ d.vertexOffset(), d.refIndexBuffer(), d.indexCount(),
+ d.indexOffset(), mUniforms,
+ SkSpan<SkRuntimeEffect::ChildPtr>(), mBounds)
+ .mesh;
+ }
+ return SkMesh::Make(mMeshSpec, mMode, d.refVertexBuffer(), d.vertexCount(),
+ d.vertexOffset(), mUniforms, SkSpan<SkRuntimeEffect::ChildPtr>(),
+ mBounds)
+ .mesh;
+ }
+
+ mutable SkMesh mMesh;
+ sk_sp<SkMeshSpecification> mMeshSpec;
+ SkMesh::Mode mMode;
+ std::shared_ptr<const MeshBufferData> mBufferData;
+ sk_sp<const SkData> mUniforms;
+ SkRect mBounds;
+ };
+
+ Mesh(sk_sp<SkMeshSpecification> meshSpec, SkMesh::Mode mode, std::vector<uint8_t> vertexData,
+ int32_t vertexCount, int32_t vertexOffset, const SkRect& bounds)
+ : Mesh(std::move(meshSpec), mode, std::move(vertexData), vertexCount, vertexOffset,
+ /* indexData = */ {}, /* indexCount = */ 0, /* indexOffset = */ 0, bounds) {}
+
+ Mesh(sk_sp<SkMeshSpecification> meshSpec, SkMesh::Mode mode, std::vector<uint8_t> vertexData,
+ int32_t vertexCount, int32_t vertexOffset, std::vector<uint8_t> indexData,
+ int32_t indexCount, int32_t indexOffset, const SkRect& bounds)
+ : mMeshSpec(std::move(meshSpec))
+ , mMode(mode)
+ , mBufferData(std::make_shared<MeshBufferData>(std::move(vertexData), vertexCount,
+ vertexOffset, std::move(indexData),
+ indexCount, indexOffset))
+ , mUniformBuilder(mMeshSpec)
, mBounds(bounds) {}
Mesh(Mesh&&) = default;
@@ -137,77 +253,22 @@
[[nodiscard]] std::tuple<bool, SkString> validate();
- void updateSkMesh(GrDirectContext* context) const {
- GrDirectContext::DirectContextID genId = GrDirectContext::DirectContextID();
- if (context) {
- genId = context->directContextID();
- }
+ std::shared_ptr<const MeshBufferData> refBufferData() const { return mBufferData; }
- if (mIsDirty || genId != mGenerationId) {
- auto vertexData = reinterpret_cast<const void*>(mVertexBufferData.data());
-#ifdef __ANDROID__
- auto vb = SkMeshes::MakeVertexBuffer(context,
- vertexData,
- mVertexBufferData.size());
-#else
- auto vb = SkMeshes::MakeVertexBuffer(vertexData,
- mVertexBufferData.size());
-#endif
- auto meshMode = SkMesh::Mode(mMode);
- if (!mIndexBufferData.empty()) {
- auto indexData = reinterpret_cast<const void*>(mIndexBufferData.data());
-#ifdef __ANDROID__
- auto ib = SkMeshes::MakeIndexBuffer(context,
- indexData,
- mIndexBufferData.size());
-#else
- auto ib = SkMeshes::MakeIndexBuffer(indexData,
- mIndexBufferData.size());
-#endif
- mMesh = SkMesh::MakeIndexed(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
- ib, mIndexCount, mIndexOffset, mBuilder->fUniforms,
- SkSpan<SkRuntimeEffect::ChildPtr>(), mBounds)
- .mesh;
- } else {
- mMesh = SkMesh::Make(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
- mBuilder->fUniforms, SkSpan<SkRuntimeEffect::ChildPtr>(),
- mBounds)
- .mesh;
- }
- mIsDirty = false;
- mGenerationId = genId;
- }
+ Snapshot takeSnapshot() const {
+ return Snapshot(mMeshSpec, mMode, mBufferData, mUniformBuilder.fUniforms, mBounds);
}
- SkMesh& getSkMesh() const {
- LOG_FATAL_IF(mIsDirty,
- "Attempt to obtain SkMesh when Mesh is dirty, did you "
- "forget to call updateSkMesh with a GrDirectContext? "
- "Defensively creating a CPU mesh");
- return mMesh;
- }
-
- void markDirty() { mIsDirty = true; }
-
- MeshUniformBuilder* uniformBuilder() { return mBuilder.get(); }
+ MeshUniformBuilder* uniformBuilder() { return &mUniformBuilder; }
private:
sk_sp<SkMeshSpecification> mMeshSpec;
- int mMode = 0;
-
- std::vector<uint8_t> mVertexBufferData;
- size_t mVertexCount = 0;
- size_t mVertexOffset = 0;
-
- std::vector<uint8_t> mIndexBufferData;
- size_t mIndexCount = 0;
- size_t mIndexOffset = 0;
-
- std::unique_ptr<MeshUniformBuilder> mBuilder;
- SkRect mBounds{};
-
- mutable SkMesh mMesh{};
- mutable bool mIsDirty = true;
- mutable GrDirectContext::DirectContextID mGenerationId = GrDirectContext::DirectContextID();
+ SkMesh::Mode mMode;
+ std::shared_ptr<MeshBufferData> mBufferData;
+ MeshUniformBuilder mUniformBuilder;
+ SkRect mBounds;
};
+
+} // namespace android
+
#endif // MESH_H_
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 755332ff..325bdd6 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -16,10 +16,6 @@
#include "Properties.h"
-#include "Debug.h"
-#ifdef __ANDROID__
-#include "HWUIProperties.sysprop.h"
-#endif
#include <android-base/properties.h>
#include <cutils/compiler.h>
#include <log/log.h>
@@ -28,6 +24,8 @@
#include <cstdlib>
#include <optional>
+#include "Debug.h"
+#include "HWUIProperties.sysprop.h"
#include "src/core/SkTraceEventCommon.h"
#ifdef __ANDROID__
@@ -47,16 +45,6 @@
namespace android {
namespace uirenderer {
-#ifndef __ANDROID__ // Layoutlib does not compile HWUIProperties.sysprop as it depends on cutils properties
-std::optional<bool> use_vulkan() {
- return base::GetBoolProperty("ro.hwui.use_vulkan", true);
-}
-
-std::optional<std::int32_t> render_ahead() {
- return base::GetIntProperty("ro.hwui.render_ahead", 0);
-}
-#endif
-
bool Properties::debugLayersUpdates = false;
bool Properties::debugOverdraw = false;
bool Properties::debugTraceGpuResourceCategories = false;
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 54aef55..d026379 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -573,9 +573,9 @@
struct DrawMesh final : Op {
static const auto kType = Type::DrawMesh;
DrawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint)
- : mesh(mesh), blender(std::move(blender)), paint(paint) {}
+ : mesh(mesh.takeSnapshot()), blender(std::move(blender)), paint(paint) {}
- const Mesh& mesh;
+ Mesh::Snapshot mesh;
sk_sp<SkBlender> blender;
SkPaint paint;
@@ -1296,14 +1296,5 @@
fDL->drawWebView(drawable);
}
-[[nodiscard]] const SkMesh& DrawMeshPayload::getSkMesh() const {
- LOG_FATAL_IF(!meshWrapper && !mesh, "One of Mesh or Mesh must be non-null");
- if (meshWrapper) {
- return meshWrapper->getSkMesh();
- } else {
- return *mesh;
- }
-}
-
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 965264f..f867852 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -41,11 +41,12 @@
enum class SkBlendMode;
class SkRRect;
-class Mesh;
namespace android {
-namespace uirenderer {
+class Mesh;
+
+namespace uirenderer {
namespace skiapipeline {
class FunctorDrawable;
}
@@ -68,18 +69,6 @@
static_assert(sizeof(DisplayListOp) == 4);
-class DrawMeshPayload {
-public:
- explicit DrawMeshPayload(const SkMesh* mesh) : mesh(mesh) {}
- explicit DrawMeshPayload(const Mesh* meshWrapper) : meshWrapper(meshWrapper) {}
-
- [[nodiscard]] const SkMesh& getSkMesh() const;
-
-private:
- const SkMesh* mesh = nullptr;
- const Mesh* meshWrapper = nullptr;
-};
-
struct DrawImagePayload {
explicit DrawImagePayload(Bitmap& bitmap)
: image(bitmap.makeImage()), palette(bitmap.palette()) {
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 0b739c3..72e83af 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -596,8 +596,8 @@
if (recordingContext) {
context = recordingContext->asDirectContext();
}
- mesh.updateSkMesh(context);
- mCanvas->drawMesh(mesh.getSkMesh(), blender, paint);
+ mesh.refBufferData()->updateBuffers(context);
+ mCanvas->drawMesh(mesh.takeSnapshot().getSkMesh(), blender, paint);
}
// ----------------------------------------------------------------------------
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 14b4f58..4eb6918 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -34,7 +34,6 @@
class SkRRect;
class SkRuntimeShaderBuilder;
class SkVertices;
-class Mesh;
namespace minikin {
class Font;
@@ -61,6 +60,7 @@
class AnimatedImageDrawable;
class Bitmap;
+class Mesh;
class Paint;
struct Typeface;
diff --git a/libs/hwui/jni/android_graphics_Mesh.cpp b/libs/hwui/jni/android_graphics_Mesh.cpp
index 5cb43e5..3109de5 100644
--- a/libs/hwui/jni/android_graphics_Mesh.cpp
+++ b/libs/hwui/jni/android_graphics_Mesh.cpp
@@ -38,8 +38,8 @@
return 0;
}
auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
- auto meshPtr = new Mesh(skMeshSpec, mode, std::move(buffer), vertexCount, vertexOffset,
- std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
+ auto meshPtr = new Mesh(skMeshSpec, static_cast<SkMesh::Mode>(mode), std::move(buffer),
+ vertexCount, vertexOffset, skRect);
auto [valid, msg] = meshPtr->validate();
if (!valid) {
jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str());
@@ -63,9 +63,9 @@
return 0;
}
auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
- auto meshPtr = new Mesh(skMeshSpec, mode, std::move(vBuf), vertexCount, vertexOffset,
- std::move(iBuf), indexCount, indexOffset,
- std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
+ auto meshPtr =
+ new Mesh(skMeshSpec, static_cast<SkMesh::Mode>(mode), std::move(vBuf), vertexCount,
+ vertexOffset, std::move(iBuf), indexCount, indexOffset, skRect);
auto [valid, msg] = meshPtr->validate();
if (!valid) {
jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str());
@@ -133,7 +133,6 @@
ScopedUtfChars name(env, uniformName);
const float values[4] = {value1, value2, value3, value4};
nativeUpdateFloatUniforms(env, wrapper->uniformBuilder(), name.c_str(), values, count, false);
- wrapper->markDirty();
}
static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring jUniformName,
@@ -143,7 +142,6 @@
AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
nativeUpdateFloatUniforms(env, wrapper->uniformBuilder(), name.c_str(), autoValues.ptr(),
autoValues.length(), isColor);
- wrapper->markDirty();
}
static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder,
@@ -166,7 +164,6 @@
ScopedUtfChars name(env, uniformName);
const int values[4] = {value1, value2, value3, value4};
nativeUpdateIntUniforms(env, wrapper->uniformBuilder(), name.c_str(), values, count);
- wrapper->markDirty();
}
static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
@@ -176,7 +173,6 @@
AutoJavaIntArray autoValues(env, values, 0);
nativeUpdateIntUniforms(env, wrapper->uniformBuilder(), name.c_str(), autoValues.ptr(),
autoValues.length());
- wrapper->markDirty();
}
static void MeshWrapper_destroy(Mesh* wrapper) {
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index 6ccb212..40dfc9d 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -16,6 +16,7 @@
#pragma once
+#include <FileBlobCache.h>
#include <GrContextOptions.h>
#include <SkRefCnt.h>
#include <cutils/compiler.h>
@@ -32,7 +33,6 @@
namespace android {
class BlobCache;
-class FileBlobCache;
namespace uirenderer {
namespace skiapipeline {
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index 5c8285a..e0216b6 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -111,8 +111,8 @@
}
auto grContext = info.canvasContext.getGrContext();
- for (auto mesh : mMeshes) {
- mesh->updateSkMesh(grContext);
+ for (const auto& bufferData : mMeshBufferData) {
+ bufferData->updateBuffers(grContext);
}
#endif
@@ -181,7 +181,7 @@
mDisplayList.reset();
- mMeshes.clear();
+ mMeshBufferData.clear();
mMutableImages.clear();
mVectorDrawables.clear();
mAnimatedImages.clear();
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index b9dc1c4..071a4e8 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -17,6 +17,7 @@
#pragma once
#include <deque>
+#include <memory>
#include "Mesh.h"
#include "RecordingCanvas.h"
@@ -172,7 +173,7 @@
std::deque<RenderNodeDrawable> mChildNodes;
std::deque<FunctorDrawable*> mChildFunctors;
std::vector<SkImage*> mMutableImages;
- std::vector<const Mesh*> mMeshes;
+ std::vector<std::shared_ptr<const MeshBufferData>> mMeshBufferData;
private:
std::vector<Pair<VectorDrawableRoot*, SkMatrix>> mVectorDrawables;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index e917f9a..45bfe1c 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -342,7 +342,7 @@
}
void SkiaRecordingCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) {
- mDisplayList->mMeshes.push_back(&mesh);
+ mDisplayList->mMeshBufferData.push_back(mesh.refBufferData());
mRecorder.drawMesh(mesh, blender, paint);
}
diff --git a/media/OWNERS b/media/OWNERS
index 994a7b8..2e9276d 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -3,14 +3,9 @@
elaurent@google.com
essick@google.com
etalvala@google.com
-hdmoon@google.com
hunga@google.com
-insun@google.com
-jaewan@google.com
-jinpark@google.com
jmtrivi@google.com
jsharkey@android.com
-klhyun@google.com
lajos@google.com
nchalko@google.com
philburk@google.com
@@ -20,8 +15,6 @@
# go/android-fwk-media-solutions for info on areas of ownership.
include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
-# SEO
-
# SEA/KIR/BVE
jtinker@google.com
robertshih@google.com
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 4be282b..4217562 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -1690,6 +1690,18 @@
}
/**
+ * Query if the usage is a hidden (neither sdk nor SystemApi) usage
+ *
+ * @param usage the {@link android.media.AudioAttributes usage}
+ * @return {@code true} if the usage is {@link AudioAttributes#USAGE_VIRTUAL_SOURCE} or
+ * {@code false} otherwise
+ * @hide
+ */
+ public static boolean isHiddenUsage(@AttributeUsage int usage) {
+ return usage == USAGE_VIRTUAL_SOURCE;
+ }
+
+ /**
* Query if the content type is a valid sdk content type
* @param contentType one of {@link AttributeContentType}
* @return {@code true} if the content type is valid for sdk or {@code false} otherwise
diff --git a/media/java/android/media/FadeManagerConfiguration.java b/media/java/android/media/FadeManagerConfiguration.java
index e6ec2c3..eaafa59 100644
--- a/media/java/android/media/FadeManagerConfiguration.java
+++ b/media/java/android/media/FadeManagerConfiguration.java
@@ -694,7 +694,8 @@
}
private static boolean isUsageValid(int usage) {
- return AudioAttributes.isSdkUsage(usage) || AudioAttributes.isSystemUsage(usage);
+ return AudioAttributes.isSdkUsage(usage) || AudioAttributes.isSystemUsage(usage)
+ || AudioAttributes.isHiddenUsage(usage);
}
private void ensureFadingIsEnabled() {
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index f5dc6ea..1905fa8 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -5101,7 +5101,7 @@
* size is larger than 16x16, then the qpOffset information of all 16x16 blocks that
* encompass the coding unit is combined and used. The QP of target block will be calculated
* as 'frameQP + offsetQP'. If the result exceeds minQP or maxQP configured then the value
- * may be clamped. Negative offset results in blocks encoded at lower QP than frame QP and
+ * will be clamped. Negative offset results in blocks encoded at lower QP than frame QP and
* positive offsets will result in encoding blocks at higher QP than frame QP. If the areas
* of negative QP and positive QP are chosen wisely, the overall viewing experience can be
* improved.
@@ -5128,7 +5128,7 @@
* quantization parameter (QP) offset of the blocks in the bounding box. The bounding box
* will get stretched outwards to align to LCU boundaries during encoding. The Qp Offset is
* integral and shall be in the range [-128, 127]. The QP of target block will be calculated
- * as frameQP + offsetQP. If the result exceeds minQP or maxQP configured then the value may
+ * as frameQP + offsetQP. If the result exceeds minQP or maxQP configured then the value will
* be clamped. Negative offset results in blocks encoded at lower QP than frame QP and
* positive offsets will result in blocks encoded at higher QP than frame QP. If the areas of
* negative QP and positive QP are chosen wisely, the overall viewing experience can be
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 1e7bc47..abad460 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -2651,11 +2651,10 @@
mBlockAspectRatioRange = POSITIVE_RATIONALS;
mAspectRatioRange = POSITIVE_RATIONALS;
- // YUV 4:2:0 requires 2:2 alignment
- mWidthAlignment = 2;
- mHeightAlignment = 2;
- mBlockWidth = 2;
- mBlockHeight = 2;
+ mWidthAlignment = 1;
+ mHeightAlignment = 1;
+ mBlockWidth = 1;
+ mBlockHeight = 1;
mSmallerDimensionUpperLimit = getSizeRange().getUpper();
}
diff --git a/media/java/android/media/browse/OWNERS b/media/java/android/media/browse/OWNERS
deleted file mode 100644
index 916fc36..0000000
--- a/media/java/android/media/browse/OWNERS
+++ /dev/null
@@ -1,8 +0,0 @@
-# Bug component: 137631
-
-hdmoon@google.com
-insun@google.com
-jaewan@google.com
-jinpark@google.com
-klhyun@google.com
-gyumin@google.com
diff --git a/media/java/android/media/session/OWNERS b/media/java/android/media/session/OWNERS
deleted file mode 100644
index 916fc36..0000000
--- a/media/java/android/media/session/OWNERS
+++ /dev/null
@@ -1,8 +0,0 @@
-# Bug component: 137631
-
-hdmoon@google.com
-insun@google.com
-jaewan@google.com
-jinpark@google.com
-klhyun@google.com
-gyumin@google.com
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
index 236b1fd..c48a956 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
@@ -45,10 +45,8 @@
@RunWith(AndroidJUnit4.class)
@RequiresFlagsEnabled(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
public final class FadeManagerConfigurationUnitTest {
- private static final long DEFAULT_FADE_OUT_DURATION_MS =
- FadeManagerConfiguration.getDefaultFadeOutDurationMillis();
- private static final long DEFAULT_FADE_IN_DURATION_MS =
- FadeManagerConfiguration.getDefaultFadeInDurationMillis();
+ private static final long DEFAULT_FADE_OUT_DURATION_MS = 2_000;
+ private static final long DEFAULT_FADE_IN_DURATION_MS = 1_000;
private static final long TEST_FADE_OUT_DURATION_MS = 1_500;
private static final long TEST_FADE_IN_DURATION_MS = 750;
private static final int TEST_INVALID_USAGE = -10;
@@ -251,6 +249,20 @@
}
@Test
+ public void testGetDefaultFadeOutDuration() {
+ expect.withMessage("Default fade out duration")
+ .that(FadeManagerConfiguration.getDefaultFadeOutDurationMillis())
+ .isEqualTo(DEFAULT_FADE_OUT_DURATION_MS);
+ }
+
+ @Test
+ public void testGetDefaultFadeInDuration() {
+ expect.withMessage("Default fade in duration")
+ .that(FadeManagerConfiguration.getDefaultFadeInDurationMillis())
+ .isEqualTo(DEFAULT_FADE_IN_DURATION_MS);
+ }
+
+ @Test
public void testSetFadeState_toDisable() {
final int fadeState = FadeManagerConfiguration.FADE_STATE_DISABLED;
FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
@@ -746,6 +758,87 @@
.isEqualTo(FadeManagerConfiguration.CREATOR.createFromParcel(parcel));
}
+ @Test
+ public void testGetFadeOutVolumeShaperConfigForUsage_withInvalidUsage_fails() {
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+ mFmc.getFadeOutVolumeShaperConfigForUsage(TEST_INVALID_USAGE)
+ );
+
+ expect.withMessage("Fade out volume shaper config for invalid usage exception")
+ .that(thrown).hasMessageThat().contains("Invalid usage");
+ }
+
+ @Test
+ public void testGetFadeInVolumeShaperConfigForUsage_withInvalidUsage_fails() {
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+ mFmc.getFadeInVolumeShaperConfigForUsage(TEST_INVALID_USAGE)
+ );
+
+ expect.withMessage("Fade in volume shaper config for invalid usage exception")
+ .that(thrown).hasMessageThat().contains("Invalid usage");
+ }
+
+ @Test
+ public void testGetFadeVolumeShaperConfigForUsage_forSdkUsages() {
+ FadeManagerConfiguration.Builder builder = new FadeManagerConfiguration.Builder();
+ for (int usage : AudioAttributes.getSdkUsages()) {
+ builder.addFadeableUsage(usage);
+ builder.setFadeOutVolumeShaperConfigForUsage(usage, TEST_FADE_OUT_VOLUME_SHAPER_CONFIG);
+ builder.setFadeInVolumeShaperConfigForUsage(usage, TEST_FADE_IN_VOLUME_SHAPER_CONFIG);
+ }
+ FadeManagerConfiguration fmc = builder.build();
+
+ for (int usage : AudioAttributes.getSdkUsages()) {
+ expect.withMessage("Fade out volume shaper config for sdk usage")
+ .that(fmc.getFadeOutVolumeShaperConfigForUsage(usage))
+ .isEqualTo(TEST_FADE_OUT_VOLUME_SHAPER_CONFIG);
+ expect.withMessage("Fade in volume shaper config for sdk usage")
+ .that(fmc.getFadeInVolumeShaperConfigForUsage(usage))
+ .isEqualTo(TEST_FADE_IN_VOLUME_SHAPER_CONFIG);
+ }
+ }
+
+ @Test
+ public void testGetFadeVolumeShaperConfigForUsage_forSystemUsages() {
+ int[] systemUsages = {AudioAttributes.USAGE_CALL_ASSISTANT, AudioAttributes.USAGE_EMERGENCY,
+ AudioAttributes.USAGE_SAFETY, AudioAttributes.USAGE_VEHICLE_STATUS,
+ AudioAttributes.USAGE_ANNOUNCEMENT};
+ FadeManagerConfiguration.Builder builder = new FadeManagerConfiguration.Builder();
+ for (int usage : systemUsages) {
+ builder.addFadeableUsage(usage);
+ builder.setFadeOutVolumeShaperConfigForUsage(usage, TEST_FADE_OUT_VOLUME_SHAPER_CONFIG);
+ builder.setFadeInVolumeShaperConfigForUsage(usage, TEST_FADE_IN_VOLUME_SHAPER_CONFIG);
+ }
+ FadeManagerConfiguration fmc = builder.build();
+
+ for (int usage : systemUsages) {
+ expect.withMessage("Fade out volume shaper config for system usage")
+ .that(fmc.getFadeOutVolumeShaperConfigForUsage(usage))
+ .isEqualTo(TEST_FADE_OUT_VOLUME_SHAPER_CONFIG);
+ expect.withMessage("Fade in volume shaper config for system usage")
+ .that(fmc.getFadeInVolumeShaperConfigForUsage(usage))
+ .isEqualTo(TEST_FADE_IN_VOLUME_SHAPER_CONFIG);
+ }
+ }
+
+ @Test
+ public void testGetFadeVolumeShaperConfigForUsage_forHiddenUsage() {
+ int hiddenUsage = AudioAttributes.USAGE_VIRTUAL_SOURCE;
+ FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+ .addFadeableUsage(hiddenUsage)
+ .setFadeOutVolumeShaperConfigForUsage(hiddenUsage,
+ TEST_FADE_OUT_VOLUME_SHAPER_CONFIG)
+ .setFadeInVolumeShaperConfigForUsage(hiddenUsage,
+ TEST_FADE_IN_VOLUME_SHAPER_CONFIG).build();
+
+ expect.withMessage("Fade out volume shaper config for hidden usage")
+ .that(fmc.getFadeOutVolumeShaperConfigForUsage(hiddenUsage))
+ .isEqualTo(TEST_FADE_OUT_VOLUME_SHAPER_CONFIG);
+ expect.withMessage("Fade in volume shaper config for hidden usage")
+ .that(fmc.getFadeInVolumeShaperConfigForUsage(hiddenUsage))
+ .isEqualTo(TEST_FADE_IN_VOLUME_SHAPER_CONFIG);
+ }
+
private static AudioAttributes createAudioAttributesForUsage(int usage) {
if (AudioAttributes.isSystemUsage(usage)) {
return new AudioAttributes.Builder().setSystemUsage(usage).build();
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 752ebdf..4812685 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -58,6 +58,7 @@
"configuration.cpp",
"hardware_buffer_jni.cpp",
"input.cpp",
+ "input_transfer_token.cpp",
"looper.cpp",
"native_activity.cpp",
"native_window_jni.cpp",
diff --git a/native/android/input_transfer_token.cpp b/native/android/input_transfer_token.cpp
new file mode 100644
index 0000000..501e1d3
--- /dev/null
+++ b/native/android/input_transfer_token.cpp
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+#define LOG_TAG "InputTransferToken"
+
+#include <android/input_transfer_token_jni.h>
+#include <android_runtime/android_window_InputTransferToken.h>
+#include <gui/InputTransferToken.h>
+#include <log/log_main.h>
+
+using namespace android;
+
+#define CHECK_NOT_NULL(name) \
+ LOG_ALWAYS_FATAL_IF(name == nullptr, "nullptr passed as " #name " argument");
+
+void InputTransferToken_acquire(InputTransferToken* inputTransferToken) {
+ // incStrong/decStrong token must be the same, doesn't matter what it is
+ inputTransferToken->incStrong((void*)InputTransferToken_acquire);
+}
+
+void InputTransferToken_release(InputTransferToken* inputTransferToken) {
+ // incStrong/decStrong token must be the same, doesn't matter what it is
+ inputTransferToken->decStrong((void*)InputTransferToken_acquire);
+}
+
+AInputTransferToken* AInputTransferToken_fromJava(JNIEnv* env, jobject inputTransferTokenObj) {
+ CHECK_NOT_NULL(env);
+ CHECK_NOT_NULL(inputTransferTokenObj);
+ InputTransferToken* inputTransferToken =
+ android_window_InputTransferToken_getNativeInputTransferToken(env,
+ inputTransferTokenObj);
+ CHECK_NOT_NULL(inputTransferToken);
+ InputTransferToken_acquire(inputTransferToken);
+ return reinterpret_cast<AInputTransferToken*>(inputTransferToken);
+}
+
+jobject AInputTransferToken_toJava(JNIEnv* _Nonnull env,
+ const AInputTransferToken* aInputTransferToken) {
+ CHECK_NOT_NULL(env);
+ CHECK_NOT_NULL(aInputTransferToken);
+ const InputTransferToken* inputTransferToken =
+ reinterpret_cast<const InputTransferToken*>(aInputTransferToken);
+ return android_window_InputTransferToken_getJavaInputTransferToken(env, inputTransferToken);
+}
+
+void AInputTransferToken_release(AInputTransferToken* aInputTransferToken) {
+ CHECK_NOT_NULL(aInputTransferToken);
+ InputTransferToken* inputTransferToken =
+ reinterpret_cast<InputTransferToken*>(aInputTransferToken);
+ InputTransferToken_release(inputTransferToken);
+}
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 35e37b2..b2925bf 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -98,6 +98,9 @@
AInputQueue_getEvent;
AInputQueue_hasEvents;
AInputQueue_preDispatchEvent;
+ AInputTransferToken_fromJava; # introduced=35
+ AInputTransferToken_release; # introduced=35
+ AInputTransferToken_toJava; # introduced=35
AKeyEvent_getAction;
AKeyEvent_getDownTime;
AKeyEvent_getEventTime;
diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.java b/nfc/java/android/nfc/cardemulation/PollingFrame.java
index 7028c8f..af63a6e 100644
--- a/nfc/java/android/nfc/cardemulation/PollingFrame.java
+++ b/nfc/java/android/nfc/cardemulation/PollingFrame.java
@@ -174,6 +174,16 @@
&& frame.getBoolean(KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT);
}
+ /**
+ * Constructor for Polling Frames.
+ *
+ * @param type the type of the frame
+ * @param data a byte array of the data contained in the frame
+ * @param gain the vendor-specific gain of the field
+ * @param timestamp the timestamp in millisecones
+ * @param triggeredAutoTransact whether or not this frame triggered the device to start a
+ * transaction automatically
+ */
public PollingFrame(@PollingFrameType int type, @Nullable byte[] data,
int gain, int timestamp, boolean triggeredAutoTransact) {
mType = type;
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
index e79176b..56b1c2e 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
@@ -56,7 +56,7 @@
headerContent = {
SignInHeader(
icon = entry.icon,
- title = stringResource(R.string.use_password_title),
+ title = stringResource(R.string.use_passkey_title),
)
},
accountContent = {
diff --git a/packages/SettingsLib/DataStore/README.md b/packages/SettingsLib/DataStore/README.md
new file mode 100644
index 0000000..30cb993
--- /dev/null
+++ b/packages/SettingsLib/DataStore/README.md
@@ -0,0 +1,164 @@
+# Datastore library
+
+This library aims to manage datastore in a consistent way.
+
+## Overview
+
+A datastore is required to extend the `BackupRestoreStorage` class and implement
+either `Observable` or `KeyedObservable` interface, which enforces:
+
+- Backup and restore: Datastore should support
+ [data backup](https://developer.android.com/guide/topics/data/backup) to
+ preserve user experiences on a new device.
+- Observer pattern: The
+ [observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) allows to
+ monitor data change in the datastore and
+ - trigger
+ [BackupManager.dataChanged](https://developer.android.com/reference/android/app/backup/BackupManager#dataChanged\(\))
+ automatically.
+ - track data change event to log metrics.
+ - update internal state and take action.
+
+### Backup and restore
+
+The Android backup framework provides
+[BackupAgentHelper](https://developer.android.com/reference/android/app/backup/BackupAgentHelper)
+and
+[BackupHelper](https://developer.android.com/reference/android/app/backup/BackupHelper)
+to back up a datastore. However, there are several caveats when implement
+`BackupHelper`:
+
+- performBackup: The data is updated incrementally but it is not well
+ documented. The `ParcelFileDescriptor` state parameters are normally ignored
+ and data is updated even there is no change.
+- restoreEntity: The implementation must take care not to seek or close the
+ underlying data source, nor read more than size() bytes from the stream when
+ restore (see
+ [BackupDataInputStream](https://developer.android.com/reference/android/app/backup/BackupDataInputStream)).
+ It is possible a `BackupHelper` prevents other `BackupHelper`s from
+ restoring data.
+- writeNewStateDescription: Existing implementations rarely notice that this
+ callback is invoked after all entities are restored, and check if necessary
+ data are all restored in `restoreEntity` (e.g.
+ [BatteryBackupHelper](https://cs.android.com/android/platform/superproject/main/+/main:packages/apps/Settings/src/com/android/settings/fuelgauge/BatteryBackupHelper.java;l=144;drc=cca804e1ed504e2d477be1e3db00fb881ca32736)),
+ which is not robust sometimes.
+
+This library provides more clear API and offers some improvements:
+
+- The implementation only needs to focus on the `BackupRestoreEntity`
+ interface. The `InputStream` of restore will ensure bounded data are read,
+ and close the stream will be no-op.
+- The library computes checksum of the backup data automatically, so that
+ unchanged data will not be sent to Android backup system.
+- Data compression is supported:
+ - ZIP best compression is enabled by default, no extra effort needs to be
+ taken.
+ - It is safe to switch between compression and no compression in future,
+ the backup data will add 1 byte header to recognize the codec.
+ - To support other compression algorithms, simply wrap over the
+ `InputStream` and `OutputStream`. Actually, the checksum is computed in
+ this way by
+ [CheckedInputStream](https://developer.android.com/reference/java/util/zip/CheckedInputStream)
+ and
+ [CheckedOutputStream](https://developer.android.com/reference/java/util/zip/CheckedOutputStream),
+ see `BackupRestoreStorage` implementation for more details.
+- Enhanced forward compatibility for file is enabled: If a backup includes
+ data that didn't exist in earlier versions of the app, the data can still be
+ successfully restored in those older versions. This is achieved by extending
+ the `BackupRestoreFileStorage` class, and `BackupRestoreFileArchiver` will
+ treat each file as an entity and do the backup / restore.
+- Manual `BackupManager.dataChanged` call is unnecessary now, the library will
+ do the invocation (see next section).
+
+### Observer pattern
+
+Manual `BackupManager.dataChanged` call is required by current backup framework.
+In practice, it is found that `SharedPreferences` usages foget to invoke the
+API. Besides, there are common use cases to log metrics when data is changed.
+Consequently, observer pattern is employed to resolve the issues.
+
+If the datastore is key-value based (e.g. `SharedPreferences`), implements the
+`KeyedObservable` interface to offer fine-grained observer. Otherwise,
+implements `Observable`. The library provides thread-safe implementations
+(`KeyedDataObservable` / `DataObservable`), and Kotlin delegation will be
+helpful.
+
+Keep in mind that the implementation should call `KeyedObservable.notifyChange`
+/ `Observable.notifyChange` whenever internal data is changed, so that the
+registered observer will be notified properly.
+
+## Usage and example
+
+For `SharedPreferences` use case, leverage the `SharedPreferencesStorage`. To
+back up other file based storage, extend the `BackupRestoreFileStorage` class.
+
+Here is an example of customized datastore, which has a string to back up:
+
+```kotlin
+class MyDataStore : ObservableBackupRestoreStorage() {
+ // Another option is make it a StringEntity type and maintain a String field inside StringEntity
+ @Volatile // backup/restore happens on Binder thread
+ var data: String? = null
+ private set
+
+ fun setData(data: String?) {
+ this.data = data
+ notifyChange(ChangeReason.UPDATE)
+ }
+
+ override val name: String
+ get() = "MyData"
+
+ override fun createBackupRestoreEntities(): List<BackupRestoreEntity> =
+ listOf(StringEntity("data"))
+
+ private inner class StringEntity(override val key: String) : BackupRestoreEntity {
+ override fun backup(
+ backupContext: BackupContext,
+ outputStream: OutputStream,
+ ) =
+ if (data != null) {
+ outputStream.write(data!!.toByteArray(UTF_8))
+ EntityBackupResult.UPDATE
+ } else {
+ EntityBackupResult.DELETE
+ }
+
+ override fun restore(restoreContext: RestoreContext, inputStream: InputStream) {
+ data = String(inputStream.readAllBytes(), UTF_8)
+ // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you
+ }
+ }
+
+ override fun onRestoreFinished() {
+ // TODO: Update state with the restored data. Use this callback instead "restore()" in case
+ // the restore action involves several entities.
+ // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you
+ }
+}
+```
+
+In the application class:
+
+```kotlin
+class MyApplication : Application() {
+ override fun onCreate() {
+ super.onCreate();
+ BackupRestoreStorageManager.getInstance(this).add(MyDataStore());
+ }
+}
+```
+
+In the custom `BackupAgentHelper` class:
+
+```kotlin
+class MyBackupAgentHelper : BackupAgentHelper() {
+ override fun onCreate() {
+ BackupRestoreStorageManager.getInstance(this).addBackupAgentHelpers(this);
+ }
+
+ override fun onRestoreFinished() {
+ BackupRestoreStorageManager.getInstance(this).onRestoreFinished();
+ }
+}
+```
diff --git a/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml b/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml
index 685e259..0ae9c26 100644
--- a/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml
+++ b/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml
@@ -19,7 +19,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="match_parent"
- android:importantForAccessibility="noHideDescendants"
+ android:importantForAccessibility="no"
android:gravity="center"
android:orientation="horizontal">
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
index f4d4dba..815a101 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
@@ -24,6 +24,7 @@
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
@@ -33,16 +34,18 @@
import android.widget.ImageView;
import androidx.annotation.RawRes;
+import androidx.annotation.StringRes;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import androidx.vectordrawable.graphics.drawable.Animatable2Compat;
+import com.android.settingslib.widget.preference.illustration.R;
+
import com.airbnb.lottie.LottieAnimationView;
import com.airbnb.lottie.LottieDrawable;
import java.io.FileNotFoundException;
import java.io.InputStream;
-import com.android.settingslib.widget.preference.illustration.R;
/**
* IllustrationPreference is a preference that can play lottie format animation
@@ -62,8 +65,8 @@
private Drawable mImageDrawable;
private View mMiddleGroundView;
private OnBindListener mOnBindListener;
-
private boolean mLottieDynamicColor;
+ private CharSequence mContentDescription;
/**
* Interface to listen in on when {@link #onBindViewHolder(PreferenceViewHolder)} occurs.
@@ -123,7 +126,10 @@
(FrameLayout) holder.findViewById(R.id.middleground_layout);
final LottieAnimationView illustrationView =
(LottieAnimationView) holder.findViewById(R.id.lottie_view);
-
+ if (illustrationView != null && !TextUtils.isEmpty(mContentDescription)) {
+ illustrationView.setContentDescription(mContentDescription);
+ illustrationView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
// To solve the problem of non-compliant illustrations, we set the frame height
// to 300dp and set the length of the short side of the screen to
// the width of the frame.
@@ -208,6 +214,29 @@
}
/**
+ * To set content description of the {@link Illustration Preference}. This can use for talkback
+ * environment if developer wants to have a customization content.
+ *
+ * @param contentDescription The CharSequence of the content description.
+ */
+ public void setContentDescription(CharSequence contentDescription) {
+ if (!TextUtils.equals(mContentDescription, contentDescription)) {
+ mContentDescription = contentDescription;
+ notifyChanged();
+ }
+ }
+
+ /**
+ * To set content description of the {@link Illustration Preference}. This can use for talkback
+ * environment if developer wants to have a customization content.
+ *
+ * @param contentDescriptionResId The resource id of the content description.
+ */
+ public void setContentDescription(@StringRes int contentDescriptionResId) {
+ setContentDescription(getContext().getText(contentDescriptionResId));
+ }
+
+ /**
* Gets the lottie illustration resource id.
*/
public int getLottieAnimationResId() {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
index 79c5ebb..5dd7caf 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
@@ -112,7 +112,8 @@
isVisible = { isVisible0 },
onDismiss = { isVisible0 = false },
buttons = listOf(
- CardButton(text = "Action") {},
+ CardButton(text = "Override") {},
+ CardButton(text = "Learn more") {},
),
),
CardModel(
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png
index 63efaf5..3e016f7 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
index bb518c0..d156f95 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png
index ae11f81..b8bb25f 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
index bb518c0..d156f95 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png
index a3692cd..63983ee 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png
index 233d088..8fcc350 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png
index 10869f2..c2f6165 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png
index 3eaecc1..f32d7421 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png
index ca61911..6659d7c 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png
index 8333e68..36cbadc 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png
index 19d0afd..7b6e702 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png
index fcfd1d8..cd44fb8 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png
index 693c592..19c028e 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png
index 0521368..8a2b800 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png
index 5bb318f..a279481 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png
index d0d014e..9aee004 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png
index 5bd1144..cc74aac 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
index 16f6b5e..f30a957 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
@@ -50,19 +50,15 @@
)
)
private val composeRule = createAndroidComposeRule<ComponentActivity>()
- private val roboRule =
- RuleChain.outerRule(deviceEmulationRule)
- .around(screenshotRule)
- .around(composeRule)
private val delegateRule =
RuleChain.outerRule(colorsRule)
- .around(roboRule)
+ .around(deviceEmulationRule)
+ .around(screenshotRule)
+ .around(composeRule)
private val matcher = UnitTestBitmapMatcher
- private val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false
override fun apply(base: Statement, description: Description): Statement {
- val ruleToApply = if (isRobolectric) roboRule else delegateRule
- return ruleToApply.apply(base, description)
+ return delegateRule.apply(base, description)
}
/**
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
index 621825a..f5cbe8f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
@@ -21,6 +21,8 @@
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@@ -165,10 +167,11 @@
}
}
+@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun Buttons(buttons: List<CardButton>, color: Color) {
if (buttons.isNotEmpty()) {
- Row(
+ FlowRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(
space = SettingsDimension.itemPaddingEnd,
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index a4bc235..8a5dfef 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1485,7 +1485,7 @@
<string name="user_nickname">Nickname</string>
<!-- Confirmation message on dialog for editing user name and profile picture. Inform user on who will be able to see the changes [CHAR LIMIT=NONE]-->
- <string name="edit_user_info_message">Your name and picture will be visible to anyone that uses this device.</string>
+ <string name="edit_user_info_message">The name and picture you choose will be visible to anyone who uses this device.</string>
<!-- Label for adding a new user in the user switcher [CHAR LIMIT=35] -->
<string name="user_add_user">Add user</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index c2c82b3..b8624fd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -1029,15 +1029,17 @@
@Override
public String toString() {
- return "CachedBluetoothDevice{"
- + "anonymizedAddress="
- + mDevice.getAnonymizedAddress()
- + ", name="
- + getName()
- + ", groupId="
- + mGroupId
- + ", member=" + mMemberDevices
- + "}";
+ StringBuilder builder = new StringBuilder("CachedBluetoothDevice{");
+ builder.append("anonymizedAddress=").append(mDevice.getAnonymizedAddress());
+ builder.append(", name=").append(getName());
+ builder.append(", groupId=").append(mGroupId);
+ builder.append(", member=").append(mMemberDevices);
+ if (isHearingAidDevice()) {
+ builder.append(", hearingAidInfo=").append(mHearingAidInfo);
+ builder.append(", subDevice=").append(mSubDevice);
+ }
+ builder.append("}");
+ return builder.toString();
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index bd27c89..1118efc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -81,11 +81,13 @@
public static final int BROADCAST_STATE_UNKNOWN = 0;
public static final int BROADCAST_STATE_ON = 1;
public static final int BROADCAST_STATE_OFF = 2;
+
@Retention(RetentionPolicy.SOURCE)
@IntDef(
prefix = {"BROADCAST_STATE_"},
value = {BROADCAST_STATE_UNKNOWN, BROADCAST_STATE_ON, BROADCAST_STATE_OFF})
public @interface BroadcastState {}
+
private static final String SETTINGS_PKG = "com.android.settings";
private static final String TAG = "LocalBluetoothLeBroadcast";
private static final boolean DEBUG = BluetoothUtils.D;
@@ -1068,7 +1070,7 @@
return;
}
int fallbackActiveGroupId = getFallbackActiveGroupId();
- if (targetCachedDevice.getGroupId() == fallbackActiveGroupId) {
+ if (getGroupId(targetCachedDevice) == fallbackActiveGroupId) {
Log.d(
TAG,
"Skip updateFallbackActiveDeviceIfNeeded, already is fallback: "
@@ -1091,6 +1093,23 @@
BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
}
+ private int getGroupId(CachedBluetoothDevice cachedDevice) {
+ int groupId = cachedDevice.getGroupId();
+ String anonymizedAddress = cachedDevice.getDevice().getAnonymizedAddress();
+ if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+ Log.d(TAG, "getGroupId by CSIP profile for device: " + anonymizedAddress);
+ return groupId;
+ }
+ for (LocalBluetoothProfile profile : cachedDevice.getProfiles()) {
+ if (profile instanceof LeAudioProfile) {
+ Log.d(TAG, "getGroupId by LEA profile for device: " + anonymizedAddress);
+ return ((LeAudioProfile) profile).getGroupId(cachedDevice.getDevice());
+ }
+ }
+ Log.d(TAG, "getGroupId return invalid id for device: " + anonymizedAddress);
+ return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
+ }
+
private void notifyBroadcastStateChange(@BroadcastState int state) {
if (!mContext.getPackageName().equals(SETTINGS_PKG)) {
Log.d(TAG, "Skip notifyBroadcastStateChange, not triggered by Settings.");
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 0df4615..21cc9a8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -161,11 +161,11 @@
override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) =
withContext(backgroundCoroutineContext) {
- if (isMuted) {
- audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_MUTE)
- } else {
- audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_UNMUTE)
- }
+ audioManager.adjustStreamVolume(
+ audioStream.value,
+ if (isMuted) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE,
+ 0,
+ )
}
private fun getMinVolume(stream: AudioStream): Int =
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
index 1586b8f..c9ac97d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
@@ -84,10 +84,10 @@
(audioStreamModel.audioStream.value == AudioManager.STREAM_NOTIFICATION &&
audioStreamModel.isMuted)
) {
- return 0
+ return audioStreamModel.minVolume
}
} else if (audioStreamModel.isMuted) {
- return 0
+ return audioStreamModel.minVolume
}
return audioStreamModel.volume
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
index 1728a80..9860cd8 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -77,13 +77,13 @@
`when`(audioManager.getStreamMaxVolume(anyInt())).thenReturn(MAX_VOLUME)
`when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
`when`(audioManager.setStreamVolume(anyInt(), anyInt(), anyInt())).then {
- val streamType = it.arguments[1] as Int
- volumeByStream[it.arguments[0] as Int] = streamType
+ val streamType = it.arguments[0] as Int
+ volumeByStream[it.arguments[0] as Int] = it.arguments[1] as Int
triggerEvent(AudioManagerEvent.StreamVolumeChanged(AudioStream(streamType)))
}
`when`(audioManager.adjustStreamVolume(anyInt(), anyInt(), anyInt())).then {
val streamType = it.arguments[0] as Int
- isMuteByStream[streamType] = it.arguments[2] == AudioManager.ADJUST_MUTE
+ isMuteByStream[streamType] = it.arguments[1] == AudioManager.ADJUST_MUTE
triggerEvent(AudioManagerEvent.StreamMuteChanged(AudioStream(streamType)))
}
`when`(audioManager.getStreamVolume(anyInt())).thenAnswer {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
index fe1529d..9c518de 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
@@ -192,7 +192,7 @@
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
final Executor executor = (command -> new Thread(command).start());
- final BluetoothVolumeControl.Callback callback = (device, volumeOffset) -> {};
+ final BluetoothVolumeControl.Callback callback = new BluetoothVolumeControl.Callback() {};
mProfile.registerCallback(executor, callback);
verify(mService).registerCallback(executor, callback);
@@ -200,7 +200,7 @@
@Test
public void unregisterCallback_verifyIsCalled() {
- final BluetoothVolumeControl.Callback callback = (device, volumeOffset) -> {};
+ final BluetoothVolumeControl.Callback callback = new BluetoothVolumeControl.Callback() {};
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
mProfile.unregisterCallback(callback);
diff --git a/packages/SettingsProvider/res/values/strings.xml b/packages/SettingsProvider/res/values/strings.xml
index 76bea31..9ca575e 100644
--- a/packages/SettingsProvider/res/values/strings.xml
+++ b/packages/SettingsProvider/res/values/strings.xml
@@ -19,14 +19,4 @@
<resources>
<!-- Name of the activity for Settings storage. -->
<string name="app_label">Settings Storage</string>
-
- <!-- A notification is shown when the user's softap config has been changed due to underlying
- hardware restrictions. This is the notifications's title.
- [CHAR_LIMIT=NONE] -->
- <string name="wifi_softap_config_change">Hotspot settings have changed</string>
-
- <!-- A notification is shown when the user's softap config has been changed due to underlying
- hardware restrictions. This is the notification's summary message.
- [CHAR_LIMIT=NONE] -->
- <string name="wifi_softap_config_change_summary">Tap to see details</string>
</resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 7b49608..e5d62f8 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -1133,16 +1133,15 @@
// Depending on device hardware, we may need to notify the user of a setting change
SoftApConfiguration storedConfig = mWifiManager.getSoftApConfiguration();
- if (isNeedToNotifyUserConfigurationHasChanged(configInCloud, storedConfig)) {
- Log.d(TAG, "restored ap configuration requires a conversion, notify the user"
+ if (isConfigurationHasChanged(configInCloud, storedConfig)) {
+ Log.d(TAG, "restored ap configuration requires a conversion: "
+ ", configInCloud is " + configInCloud + " but storedConfig is "
+ storedConfig);
- WifiSoftApConfigChangedNotifier.notifyUserOfConfigConversion(this);
}
}
}
- private boolean isNeedToNotifyUserConfigurationHasChanged(SoftApConfiguration configInCloud,
+ private boolean isConfigurationHasChanged(SoftApConfiguration configInCloud,
SoftApConfiguration storedConfig) {
// Check if the cloud configuration was modified when restored to the device.
// All elements of the configuration are compared except:
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 3266c12..b151a53 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -60,10 +60,12 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
@@ -161,6 +163,11 @@
private static final String APEX_DIR = "/apex";
private static final String APEX_ACONFIG_PATH_SUFFIX = "/etc/aconfig_flags.pb";
+ private static final String STORAGE_MIGRATION_FLAG =
+ "core_experiments_team_internal/com.android.providers.settings.storage_test_mission_1";
+ private static final String STORAGE_MIGRATION_LOG =
+ "/metadata/aconfig/flags/storage_migration.log";
+
/**
* This tag is applied to all aconfig default value-loaded flags.
*/
@@ -1439,6 +1446,20 @@
}
}
+ if (name != null && name.equals(STORAGE_MIGRATION_FLAG) && value.equals("true")) {
+ File file = new File(STORAGE_MIGRATION_LOG);
+ if (!file.exists()) {
+ try (BufferedWriter writer =
+ new BufferedWriter(new FileWriter(STORAGE_MIGRATION_LOG))) {
+ final long timestamp = System.currentTimeMillis();
+ String entry = String.format("%d | Log init", timestamp);
+ writer.write(entry);
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "failed to write storage migration file", e);
+ }
+ }
+ }
+
mSettings.put(name, new Setting(name, value, defaultValue, packageName, tag,
fromSystem, id, isPreservedInRestore));
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java b/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java
deleted file mode 100644
index dc51c40..0000000
--- a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2019 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.providers.settings;
-
-import android.app.ActivityManager;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.os.UserHandle;
-import android.provider.Settings;
-
-import com.android.internal.messages.nano.SystemMessageProto;
-import com.android.internal.notification.SystemNotificationChannels;
-
-import java.util.List;
-
-/**
- * Helper class for sending notifications when the user's Soft AP config was changed upon restore.
- */
-public class WifiSoftApConfigChangedNotifier {
- private WifiSoftApConfigChangedNotifier() {}
-
- /**
- * Send a notification informing the user that their' Soft AP Config was changed upon restore.
- * When the user taps on the notification, they are taken to the Wifi Tethering page in
- * Settings.
- */
- public static void notifyUserOfConfigConversion(Context context) {
- NotificationManager notificationManager =
- context.getSystemService(NotificationManager.class);
-
- // create channel, or update it if it already exists
- NotificationChannel channel = new NotificationChannel(
- SystemNotificationChannels.NETWORK_STATUS,
- context.getString(
- com.android.internal.R.string.notification_channel_network_status),
- NotificationManager.IMPORTANCE_LOW);
- notificationManager.createNotificationChannel(channel);
-
- notificationManager.notify(
- SystemMessageProto.SystemMessage.NOTE_SOFTAP_CONFIG_CHANGED,
- createConversionNotification(context));
- }
-
- private static Notification createConversionNotification(Context context) {
- Resources resources = context.getResources();
- CharSequence title = resources.getText(R.string.wifi_softap_config_change);
- CharSequence contentSummary = resources.getText(R.string.wifi_softap_config_change_summary);
- int color = resources.getColor(
- android.R.color.system_notification_accent_color, context.getTheme());
-
- return new Notification.Builder(context, SystemNotificationChannels.NETWORK_STATUS)
- .setSmallIcon(R.drawable.ic_wifi_settings)
- .setPriority(Notification.PRIORITY_HIGH)
- .setCategory(Notification.CATEGORY_SYSTEM)
- .setContentTitle(title)
- .setContentText(contentSummary)
- .setContentIntent(getPendingActivity(context))
- .setTicker(title)
- .setShowWhen(false)
- .setLocalOnly(true)
- .setColor(color)
- .setStyle(new Notification.BigTextStyle()
- .setBigContentTitle(title)
- .setSummaryText(contentSummary))
- .setAutoCancel(true)
- .build();
- }
-
- private static PendingIntent getPendingActivity(Context context) {
- Intent intent = new Intent("com.android.settings.WIFI_TETHER_SETTINGS")
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .setPackage(getSettingsPackageName(context));
- return PendingIntent.getActivity(context, 0, intent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
- }
-
- /**
- * @return Get settings package name.
- */
- private static String getSettingsPackageName(Context context) {
- if (context == null) return null;
-
- Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS);
- List<ResolveInfo> resolveInfos = context.getPackageManager().queryIntentActivitiesAsUser(
- intent, PackageManager.MATCH_SYSTEM_ONLY | PackageManager.MATCH_DEFAULT_ONLY,
- UserHandle.of(ActivityManager.getCurrentUser()));
- if (resolveInfos == null || resolveInfos.isEmpty()) {
- return "com.android.settings";
- }
- return resolveInfos.get(0).activityInfo.packageName;
- }
-}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index 2e14e9b..c572bdb 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -33,3 +33,10 @@
bug: "327383546"
is_fixed_read_only: true
}
+
+flag {
+ name: "storage_test_mission_1"
+ namespace: "core_experiments_team_internal"
+ description: "If this flag is detected as true on boot, writes a logfile to track storage migration correctness."
+ bug: "328444881"
+}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index eb2d13d..43ea3ec 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -698,6 +698,9 @@
<!-- Permission required for CTS test - CtsWearableSensingServiceTestCases -->
<uses-permission android:name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE" />
+ <!-- Permission required for CTS test - OnDeviceIntelligenceManagerTest -->
+ <uses-permission android:name="android.permission.USE_ON_DEVICE_INTELLIGENCE" />
+
<!-- Permission required for CTS test - CallAudioInterceptionTest -->
<uses-permission android:name="android.permission.CALL_AUDIO_INTERCEPTION" />
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index e62c77dc..d9c371a 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -434,7 +434,7 @@
</intent-filter>
</receiver>
- <activity android:name=".screenshot.LongScreenshotActivity"
+ <activity android:name=".screenshot.scroll.LongScreenshotActivity"
android:theme="@style/LongScreenshotActivity"
android:process=":screenshot"
android:exported="false"
@@ -529,15 +529,6 @@
</intent-filter>
</activity-alias>
- <!-- Springboard for launching the share and edit activity. This needs to be in the main
- system ui process since we need to notify the status bar to dismiss the keyguard -->
- <receiver android:name=".screenshot.ActionProxyReceiver"
- android:exported="false" />
-
- <!-- Callback for deleting screenshot notification -->
- <receiver android:name=".screenshot.DeleteScreenshotReceiver"
- android:exported="false" />
-
<!-- Callback for invoking a smart action from the screenshot notification. -->
<receiver android:name=".screenshot.SmartActionsReceiver"
android:exported="false"/>
@@ -911,7 +902,7 @@
<activity
android:name=".volume.panel.ui.activity.VolumePanelActivity"
- android:label="@string/sound_settings"
+ android:label="@string/accessibility_volume_settings"
android:excludeFromRecents="true"
android:exported="false"
android:launchMode="singleInstance"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 390daa5..79ae389 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -345,6 +345,16 @@
}
flag {
+ name: "activity_transition_use_largest_window"
+ namespace: "systemui"
+ description: "Target largest opening window during activity transitions."
+ bug: "323294573"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "centralized_status_bar_height_fix"
namespace: "systemui"
description: "Refactors shade header and keyguard status bar to read status bar dimens from a"
@@ -463,6 +473,13 @@
}
flag {
+ name: "enable_contextual_tips_frequency_cap"
+ description: "Enables frequency capping for contextual tips, e.g. 1x/day, 2x/week, 3x/lifetime."
+ namespace: "systemui"
+ bug: "322891421"
+}
+
+flag {
name: "enable_contextual_tips"
description: "Enables showing contextual tips."
namespace: "systemui"
@@ -628,3 +645,11 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "qs_ui_refactor"
+ namespace: "systemui"
+ description: "Enables the new QS UI pipeline that follows recommended architecture and uses"
+ " Compose for the UI."
+ bug: "325099249"
+}
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index 99b7c36..2268d16 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -44,6 +44,7 @@
"androidx.core_core-animation-nodeps",
"androidx.core_core-ktx",
"androidx.annotation_annotation",
+ "com_android_systemui_flags_lib",
"SystemUIShaderLib",
"WindowManager-Shell-shared",
"animationlib",
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index 1b99e19..ea1cb34 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -43,6 +43,7 @@
import com.android.app.animation.Interpolators
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
+import com.android.systemui.Flags.activityTransitionUseLargestWindow
import kotlin.math.roundToInt
private const val TAG = "ActivityTransitionAnimator"
@@ -648,11 +649,27 @@
var candidate: RemoteAnimationTarget? = null
for (it in apps) {
if (it.mode == RemoteAnimationTarget.MODE_OPENING) {
- if (!it.hasAnimatingParent) {
- return it
- }
- if (candidate == null) {
- candidate = it
+ if (activityTransitionUseLargestWindow()) {
+ if (
+ candidate == null ||
+ !it.hasAnimatingParent && candidate.hasAnimatingParent
+ ) {
+ candidate = it
+ continue
+ }
+ if (
+ !it.hasAnimatingParent &&
+ it.screenSpaceBounds.hasGreaterAreaThan(candidate.screenSpaceBounds)
+ ) {
+ candidate = it
+ }
+ } else {
+ if (!it.hasAnimatingParent) {
+ return it
+ }
+ if (candidate == null) {
+ candidate = it
+ }
}
}
}
@@ -960,5 +977,9 @@
e.printStackTrace()
}
}
+
+ private fun Rect.hasGreaterAreaThan(other: Rect): Boolean {
+ return (this.width() * this.height()) > (other.width() * other.height())
+ }
}
}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
index 9a99649..f779cf36 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
@@ -62,6 +62,7 @@
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.res.colorResource
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
@@ -69,7 +70,6 @@
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastFirst
import androidx.compose.ui.util.fastFirstOrNull
-import com.android.compose.theme.LocalAndroidColorScheme
/**
* Platform slider implementation that displays a slider with an [icon] and a [label] at the start.
@@ -143,12 +143,14 @@
thumb = { Spacer(Modifier.size(thumbSize)) },
)
- Spacer(
- Modifier.padding(8.dp)
- .size(4.dp)
- .align(Alignment.CenterEnd)
- .background(color = colors.indicatorColor, shape = CircleShape)
- )
+ if (enabled) {
+ Spacer(
+ Modifier.padding(8.dp)
+ .size(4.dp)
+ .align(Alignment.CenterEnd)
+ .background(color = colors.indicatorColor, shape = CircleShape)
+ )
+ }
}
}
@@ -219,9 +221,9 @@
)
Box(
modifier =
- Modifier.layoutId(TrackComponent.Label).offset {
- IntOffset(offsetX.toInt(), 0)
- },
+ Modifier.layoutId(TrackComponent.Label)
+ .offset { IntOffset(offsetX.toInt(), 0) }
+ .padding(end = 16.dp),
contentAlignment = Alignment.CenterStart,
) {
CompositionLocalProvider(
@@ -452,11 +454,11 @@
@Composable
private fun lightThemePlatformSliderColors() =
PlatformSliderColors(
- trackColor = LocalAndroidColorScheme.current.tertiaryFixedDim,
+ trackColor = colorResource(android.R.color.system_accent3_200),
indicatorColor = MaterialTheme.colorScheme.tertiary,
iconColor = MaterialTheme.colorScheme.onTertiary,
labelColorOnIndicator = MaterialTheme.colorScheme.onTertiary,
- labelColorOnTrack = LocalAndroidColorScheme.current.onTertiaryFixed,
+ labelColorOnTrack = MaterialTheme.colorScheme.onTertiaryContainer,
disabledTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
disabledIndicatorColor = MaterialTheme.colorScheme.surfaceContainerHighest,
disabledIconColor = MaterialTheme.colorScheme.outline,
@@ -467,11 +469,11 @@
@Composable
private fun darkThemePlatformSliderColors() =
PlatformSliderColors(
- trackColor = MaterialTheme.colorScheme.tertiary,
+ trackColor = colorResource(android.R.color.system_accent3_600),
indicatorColor = MaterialTheme.colorScheme.tertiary,
- iconColor = MaterialTheme.colorScheme.onTertiaryContainer,
+ iconColor = MaterialTheme.colorScheme.onTertiary,
labelColorOnIndicator = MaterialTheme.colorScheme.onTertiary,
- labelColorOnTrack = LocalAndroidColorScheme.current.onTertiaryFixed,
+ labelColorOnTrack = colorResource(android.R.color.system_accent3_900),
disabledTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
disabledIndicatorColor = MaterialTheme.colorScheme.surfaceContainerHighest,
disabledIconColor = MaterialTheme.colorScheme.outline,
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 0d6b710..6a510bd 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
@@ -85,6 +85,8 @@
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.key.onPreviewKeyEvent
+import androidx.compose.ui.input.pointer.motionEventSpy
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
@@ -177,33 +179,43 @@
}
.thenIf(!viewModel.isEditMode) {
Modifier.pointerInput(
- gridState,
- contentOffset,
- communalContent,
- gridCoordinates
- ) {
- detectLongPressGesture { offset ->
- // Deduct both grid offset relative to its container and content offset.
- val adjustedOffset =
- gridCoordinates?.let {
- offset - it.positionInWindow() - contentOffset
+ gridState,
+ contentOffset,
+ communalContent,
+ gridCoordinates
+ ) {
+ detectLongPressGesture { offset ->
+ // Deduct both grid offset relative to its container and content
+ // offset.
+ val adjustedOffset =
+ gridCoordinates?.let {
+ offset - it.positionInWindow() - contentOffset
+ }
+ val index =
+ adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
+ // Display the button only when the gesture initiates from widgets,
+ // the CTA tile, or an empty area on the screen. UMO/smartspace have
+ // their own long-press handlers. To prevent user confusion, we
+ // should
+ // not display this button.
+ if (
+ index == null ||
+ communalContent[index].isWidgetContent() ||
+ communalContent[index] is
+ CommunalContentModel.CtaTileInViewMode
+ ) {
+ isButtonToEditWidgetsShowing = true
}
- val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
- // Display the button only when the gesture initiates from widgets,
- // the CTA tile, or an empty area on the screen. UMO/smartspace have
- // their own long-press handlers. To prevent user confusion, we should
- // not display this button.
- if (
- index == null ||
- communalContent[index].isWidgetContent() ||
- communalContent[index] is CommunalContentModel.CtaTileInViewMode
- ) {
- isButtonToEditWidgetsShowing = true
+ val key =
+ index?.let { keyAtIndexIfEditable(communalContent, index) }
+ viewModel.setSelectedKey(key)
}
- val key = index?.let { keyAtIndexIfEditable(communalContent, index) }
- viewModel.setSelectedKey(key)
}
- }
+ .onPreviewKeyEvent {
+ onKeyEvent(viewModel)
+ false
+ }
+ .motionEventSpy { onMotionEvent(viewModel) }
},
) {
CommunalHubLazyGrid(
@@ -311,6 +323,14 @@
}
}
+private fun onKeyEvent(viewModel: BaseCommunalViewModel) {
+ viewModel.signalUserInteraction()
+}
+
+private fun onMotionEvent(viewModel: BaseCommunalViewModel) {
+ viewModel.signalUserInteraction()
+}
+
@Composable
private fun ScrollOnNewSmartspaceEffect(
viewModel: BaseCommunalViewModel,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index 5d9b014..eedff89 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -66,6 +66,7 @@
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.animation.Expandable
+import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.background
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.theme.colorAttr
@@ -77,16 +78,16 @@
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.qs.ui.composable.QuickSettingsTheme
import com.android.systemui.res.R
import kotlinx.coroutines.launch
@Composable
-fun FooterActionsWithAnimatedVisibility(
+fun SceneScope.FooterActionsWithAnimatedVisibility(
viewModel: FooterActionsViewModel,
isCustomizing: Boolean,
lifecycleOwner: LifecycleOwner,
- footerActionsModifier: (Modifier) -> Modifier,
modifier: Modifier = Modifier,
) {
AnimatedVisibility(visible = !isCustomizing, modifier = modifier.fillMaxWidth()) {
@@ -96,7 +97,7 @@
FooterActions(
viewModel = viewModel,
qsVisibilityLifecycleOwner = lifecycleOwner,
- modifier = footerActionsModifier(Modifier),
+ modifier = Modifier.element(QuickSettings.Elements.FooterActions),
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 6ae1410..5b9213a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -60,7 +60,6 @@
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.qs.footer.ui.compose.FooterActions
import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
import com.android.systemui.res.R
@@ -249,9 +248,6 @@
viewModel = footerActionsViewModel,
isCustomizing = isCustomizing,
lifecycleOwner = lifecycleOwner,
- footerActionsModifier = { modifier ->
- modifier.element(QuickSettings.Elements.FooterActions)
- },
modifier = Modifier.align(Alignment.CenterHorizontally),
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 31201c2f..15e7b51 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -347,9 +347,6 @@
viewModel = footerActionsViewModel,
isCustomizing = isCustomizing,
lifecycleOwner = lifecycleOwner,
- footerActionsModifier = { modifier ->
- modifier.element(QuickSettings.Elements.FooterActions)
- },
modifier = Modifier.align(Alignment.CenterHorizontally),
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
index 5f7bd47..b721e41 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
@@ -32,6 +32,11 @@
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.android.compose.animation.Expandable
@@ -52,6 +57,7 @@
override fun VolumePanelComposeScope.Content(modifier: Modifier) {
val viewModelByState by viewModelFlow.collectAsState()
val viewModel = viewModelByState ?: return
+ val label = viewModel.label.toString()
Column(
modifier = modifier,
@@ -59,7 +65,11 @@
horizontalAlignment = Alignment.CenterHorizontally,
) {
Expandable(
- modifier = Modifier.height(64.dp).fillMaxWidth(),
+ modifier =
+ Modifier.height(64.dp).fillMaxWidth().semantics {
+ role = Role.Button
+ contentDescription = label
+ },
color = MaterialTheme.colorScheme.primaryContainer,
shape = RoundedCornerShape(28.dp),
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
@@ -71,7 +81,8 @@
}
}
Text(
- text = viewModel.label.toString(),
+ modifier = Modifier.clearAndSetSemantics {},
+ text = label,
style = MaterialTheme.typography.labelMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index dfee684..28fd785 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -32,6 +32,9 @@
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.android.systemui.common.ui.compose.Icon
@@ -50,13 +53,16 @@
override fun VolumePanelComposeScope.Content(modifier: Modifier) {
val viewModelByState by viewModelFlow.collectAsState()
val viewModel = viewModelByState ?: return
+ val label = viewModel.label.toString()
+
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
OutlinedIconToggleButton(
- modifier = Modifier.height(64.dp).fillMaxWidth(),
+ modifier =
+ Modifier.height(64.dp).fillMaxWidth().semantics { contentDescription = label },
checked = viewModel.isChecked,
onCheckedChange = onCheckedChange,
shape = RoundedCornerShape(28.dp),
@@ -72,7 +78,8 @@
Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
}
Text(
- text = viewModel.label.toString(),
+ modifier = Modifier.clearAndSetSemantics {},
+ text = label,
style = MaterialTheme.typography.labelMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
index 53de5bc..6f2ed81 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
@@ -49,10 +49,16 @@
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.LiveRegionMode
+import androidx.compose.ui.semantics.liveRegion
+import androidx.compose.ui.semantics.onClick
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.android.compose.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.common.ui.compose.toColor
+import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.ConnectedDeviceViewModel
import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.DeviceIconViewModel
import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.MediaOutputViewModel
@@ -74,14 +80,19 @@
viewModel.connectedDeviceViewModel.collectAsState()
val deviceIconViewModel: DeviceIconViewModel? by
viewModel.deviceIconViewModel.collectAsState()
+ val clickLabel = stringResource(R.string.volume_panel_enter_media_output_settings)
Expandable(
- modifier = Modifier.fillMaxWidth().height(80.dp),
+ modifier =
+ Modifier.fillMaxWidth().height(80.dp).semantics {
+ liveRegion = LiveRegionMode.Polite
+ this.onClick(label = clickLabel) { false }
+ },
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(28.dp),
onClick = { viewModel.onBarClick(it) },
) { _ ->
- Row(verticalAlignment = Alignment.CenterVertically) {
+ Row(modifier = Modifier, verticalAlignment = Alignment.CenterVertically) {
connectedDeviceViewModel?.let { ConnectedDeviceText(it) }
deviceIconViewModel?.let { ConnectedDeviceIcon(it) }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
index 89251939..26086d1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
@@ -24,13 +24,16 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import com.android.compose.PlatformIconButton
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.res.R
@@ -101,16 +104,19 @@
}
}
- PlatformIconButton(
+ IconButton(
modifier = Modifier.align(Alignment.TopEnd).size(64.dp).padding(20.dp),
- iconResource = R.drawable.ic_close,
- contentDescription = null,
onClick = { dialog.dismiss() },
colors =
IconButtonDefaults.iconButtonColors(
contentColor = MaterialTheme.colorScheme.outline
)
- )
+ ) {
+ Icon(
+ painterResource(R.drawable.ic_close),
+ contentDescription = stringResource(R.string.accessibility_desc_close),
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index 81d2da0..e1cf3a5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -45,6 +45,11 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.unit.dp
import com.android.compose.PlatformSliderColors
import com.android.systemui.res.R
@@ -76,16 +81,26 @@
VolumeSlider(
modifier = Modifier.weight(1f),
state = sliderState,
- onValueChangeFinished = { sliderViewModel.onValueChangeFinished(sliderState, it) },
+ onValueChange = { newValue: Float ->
+ sliderViewModel.onValueChanged(sliderState, newValue)
+ },
+ onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
sliderColors = sliderColors,
)
+ val expandButtonStateDescription =
+ if (isExpanded) stringResource(R.string.volume_panel_expanded_sliders)
+ else stringResource(R.string.volume_panel_collapsed_sliders)
if (isExpandable) {
ExpandButton(
+ modifier =
+ Modifier.semantics {
+ role = Role.Switch
+ stateDescription = expandButtonStateDescription
+ },
isExpanded = isExpanded,
onExpandedChanged = onExpandedChanged,
- sliderColors,
- Modifier,
+ sliderColors = sliderColors,
)
}
}
@@ -114,9 +129,10 @@
VolumeSlider(
modifier = Modifier.fillMaxWidth().padding(top = 16.dp),
state = sliderState,
- onValueChangeFinished = {
- sliderViewModel.onValueChangeFinished(sliderState, it)
+ onValueChange = { newValue: Float ->
+ sliderViewModel.onValueChanged(sliderState, newValue)
},
+ onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
sliderColors = sliderColors,
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
index 910ee72..b284c69 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
@@ -43,7 +43,10 @@
VolumeSlider(
modifier = Modifier.fillMaxWidth(),
state = sliderState,
- onValueChangeFinished = { sliderViewModel.onValueChangeFinished(sliderState, it) },
+ onValueChange = { newValue: Float ->
+ sliderViewModel.onValueChanged(sliderState, newValue)
+ },
+ onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
sliderColors = sliderColors,
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index 3e0aee5..761292b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -18,19 +18,27 @@
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
-import androidx.compose.animation.expandVertically
-import androidx.compose.animation.shrinkVertically
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.IconButtonColors
+import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableFloatStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.semantics.ProgressBarRangeInfo
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.disabled
+import androidx.compose.ui.semantics.progressBarRangeInfo
+import androidx.compose.ui.semantics.setProgress
import androidx.compose.ui.unit.dp
import com.android.compose.PlatformSlider
import com.android.compose.PlatformSliderColors
@@ -40,23 +48,61 @@
@Composable
fun VolumeSlider(
state: SliderState,
- onValueChangeFinished: (Float) -> Unit,
+ onValueChange: (newValue: Float) -> Unit,
+ onIconTapped: () -> Unit,
modifier: Modifier = Modifier,
sliderColors: PlatformSliderColors,
) {
- var value by remember(state.value) { mutableFloatStateOf(state.value) }
+ val value by
+ animateFloatAsState(targetValue = state.value, label = "VolumeSliderValueAnimation")
PlatformSlider(
- modifier = modifier,
+ modifier =
+ modifier.clearAndSetSemantics {
+ if (!state.isEnabled) disabled()
+ contentDescription = state.label
+
+ // provide a not animated value to the a11y because it fails to announce the settled
+ // value when it changes rapidly.
+ progressBarRangeInfo = ProgressBarRangeInfo(state.value, state.valueRange)
+ setProgress { targetValue ->
+ val targetDirection =
+ when {
+ targetValue > value -> 1
+ targetValue < value -> -1
+ else -> 0
+ }
+
+ val newValue =
+ (value + targetDirection * state.a11yStep).coerceIn(
+ state.valueRange.start,
+ state.valueRange.endInclusive
+ )
+ onValueChange(newValue)
+ true
+ }
+ },
value = value,
valueRange = state.valueRange,
- onValueChange = { value = it },
- onValueChangeFinished = { onValueChangeFinished(value) },
+ onValueChange = onValueChange,
enabled = state.isEnabled,
icon = { isDragging ->
if (isDragging) {
- Text(text = value.toInt().toString())
+ Text(text = state.valueText, color = LocalContentColor.current)
} else {
- state.icon?.let { Icon(modifier = Modifier.size(24.dp), icon = it) }
+ state.icon?.let {
+ IconButton(
+ onClick = onIconTapped,
+ colors =
+ IconButtonColors(
+ contentColor = LocalContentColor.current,
+ containerColor = Color.Transparent,
+ disabledContentColor = LocalContentColor.current,
+ disabledContainerColor = Color.Transparent,
+ )
+ ) {
+ Icon(modifier = Modifier.size(24.dp), icon = it)
+ }
+ }
}
},
colors = sliderColors,
@@ -66,19 +112,21 @@
modifier = Modifier.basicMarquee(),
text = state.label,
style = MaterialTheme.typography.titleMedium,
+ color = LocalContentColor.current,
maxLines = 1,
)
state.disabledMessage?.let { message ->
AnimatedVisibility(
!state.isEnabled,
- enter = expandVertically { it },
- exit = shrinkVertically { it },
+ enter = slideInVertically { it },
+ exit = slideOutVertically { it },
) {
Text(
modifier = Modifier.basicMarquee(),
text = message,
style = MaterialTheme.typography.bodySmall,
+ color = LocalContentColor.current,
maxLines = 1,
)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 63ec54f..82083f9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -605,6 +605,8 @@
override val isInitiatedByUserInput = true
+ override var bouncingScene: SceneKey? = null
+
/** The current offset caused by the drag gesture. */
var dragOffset by mutableFloatStateOf(0f)
@@ -694,14 +696,31 @@
): OffsetAnimation {
return startOffsetAnimation {
val animatable = Animatable(dragOffset, OffsetVisibilityThreshold)
+ val isTargetGreater = targetOffset > animatable.value
val job =
coroutineScope
.launch {
- animatable.animateTo(
- targetValue = targetOffset,
- animationSpec = swipeSpec,
- initialVelocity = initialVelocity,
- )
+ try {
+ animatable.animateTo(
+ targetValue = targetOffset,
+ animationSpec = swipeSpec,
+ initialVelocity = initialVelocity,
+ ) {
+ if (bouncingScene == null) {
+ val isBouncing =
+ if (isTargetGreater) {
+ value > targetOffset
+ } else {
+ value < targetOffset
+ }
+ if (isBouncing) {
+ bouncingScene = targetScene
+ }
+ }
+ }
+ } finally {
+ bouncingScene = null
+ }
}
// Make sure that we settle to target scene at the end of the animation or if
// the animation is cancelled.
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 c7186da..f1177a8 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
@@ -588,7 +588,8 @@
// TODO(b/290184746): Make sure that we don't overflow transformations associated to a
// range.
val directionSign = if (transition.isUpOrLeft) -1 else 1
- val overscrollProgress = transition.progress.let { if (it > 1f) it - 1f else it }
+ val isToScene = overscroll.scene == transition.toScene
+ val overscrollProgress = transition.progress.let { if (isToScene) it - 1f else it }
val progress = directionSign * overscrollProgress
val rangeProgress = propertySpec.range?.progress(progress) ?: progress
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 e6f5d58..617a8ea 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
@@ -255,6 +255,12 @@
*/
val overscrollScope: OverscrollScope
+ /**
+ * The scene around which the transition is currently bouncing. When not `null`, this
+ * transition is currently oscillating around this scene and will soon settle to that scene.
+ */
+ val bouncingScene: SceneKey?
+
companion object {
const val DistanceUnspecified = 0f
}
@@ -287,9 +293,10 @@
val transition = currentTransition ?: return null
if (transition !is TransitionState.HasOverscrollProperties) return null
val progress = transition.progress
+ val bouncingScene = transition.bouncingScene
return when {
- progress < 0f -> fromOverscrollSpec
- progress > 1f -> toOverscrollSpec
+ progress < 0f || bouncingScene == transition.fromScene -> fromOverscrollSpec
+ progress > 1f || bouncingScene == transition.toScene -> toOverscrollSpec
else -> null
}
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 26e01fe..0804761 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -16,6 +16,8 @@
package com.android.compose.animation.scene
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.Orientation
@@ -752,4 +754,55 @@
assertThat(state.currentOverscrollSpec).isNotNull()
fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 1.5f)
}
+
+ @Test
+ fun elementTransitionWithDistanceDuringOverscrollBouncing() {
+ val layoutWidth = 200.dp
+ val layoutHeight = 400.dp
+ val state =
+ setupOverscrollScenario(
+ layoutWidth = layoutWidth,
+ layoutHeight = layoutHeight,
+ sceneTransitions = {
+ defaultSwipeSpec =
+ spring(
+ dampingRatio = Spring.DampingRatioMediumBouncy,
+ stiffness = Spring.StiffnessLow,
+ )
+
+ overscroll(TestScenes.SceneB, Orientation.Vertical) {
+ // On overscroll 100% -> Foo should translate by layoutHeight
+ translate(TestElements.Foo, y = { absoluteDistance })
+ }
+ },
+ firstScroll = 1f, // 100% scroll
+ )
+
+ val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true)
+ fooElement.assertTopPositionInRootIsEqualTo(0.dp)
+
+ rule.onRoot().performTouchInput {
+ // Scroll another 50%
+ moveBy(Offset(0f, layoutHeight.toPx() * 0.5f), delayMillis = 1_000)
+ }
+
+ val transition = state.currentTransition
+ assertThat(transition).isNotNull()
+ transition as TransitionState.HasOverscrollProperties
+
+ // Scroll 150% (100% scroll + 50% overscroll)
+ assertThat(transition.progress).isEqualTo(1.5f)
+ assertThat(state.currentOverscrollSpec).isNotNull()
+ fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * (transition.progress - 1f))
+
+ // finger raised
+ rule.onRoot().performTouchInput { up() }
+
+ // The target value is 1f, but the spring (defaultSwipeSpec) allows you to go to a lower
+ // value.
+ rule.waitUntil(timeoutMillis = 10_000) { transition.progress < 1f }
+
+ assertThat(state.currentOverscrollSpec).isNotNull()
+ assertThat(transition.bouncingScene).isEqualTo(transition.toScene)
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
index 73a66c6..a32fe22 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
@@ -27,6 +27,7 @@
isInitiatedByUserInput: Boolean = false,
isUserInputOngoing: Boolean = false,
isUpOrLeft: Boolean = false,
+ bouncingScene: SceneKey? = null,
orientation: Orientation = Orientation.Horizontal,
): TransitionState.Transition {
return object : TransitionState.Transition(from, to), TransitionState.HasOverscrollProperties {
@@ -37,6 +38,7 @@
override val isInitiatedByUserInput: Boolean = isInitiatedByUserInput
override val isUserInputOngoing: Boolean = isUserInputOngoing
override val isUpOrLeft: Boolean = isUpOrLeft
+ override val bouncingScene: SceneKey? = bouncingScene
override val orientation: Orientation = orientation
override val overscrollScope: OverscrollScope =
object : OverscrollScope {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
index adf4fc6..b253309 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -140,6 +140,14 @@
}
@Test
+ fun canShowAlternateBouncerForFingerprint_primaryBouncerShowing() {
+ givenCanShowAlternateBouncer()
+ bouncerRepository.setPrimaryShow(true)
+
+ assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+ }
+
+ @Test
fun show_whenCannotShow() {
givenCannotShowAlternateBouncer()
@@ -202,7 +210,7 @@
} else {
bouncerRepository.setAlternateBouncerUIAvailable(true)
}
-
+ bouncerRepository.setPrimaryShow(false)
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index ce6445b..43266bf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal
+import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -25,13 +26,17 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dock.dockManager
import com.android.systemui.dock.fakeDockManager
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
@@ -54,11 +59,15 @@
@Before
fun setUp() {
with(kosmos) {
+ fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT)
+
underTest =
CommunalSceneStartable(
dockManager = dockManager,
communalInteractor = communalInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
+ keyguardInteractor = keyguardInteractor,
+ systemSettings = fakeSettings,
applicationScope = applicationCoroutineScope,
bgScope = applicationCoroutineScope,
)
@@ -246,6 +255,132 @@
}
}
+ @Test
+ fun hubTimeout_whenDreaming_goesToBlank() =
+ with(kosmos) {
+ testScope.runTest {
+ // Device is dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(true)
+ communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+ val scene by collectLastValue(communalInteractor.desiredScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Scene times out back to blank after the screen timeout.
+ advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
+ }
+ }
+
+ @Test
+ fun hubTimeout_notDreaming_staysOnCommunal() =
+ with(kosmos) {
+ testScope.runTest {
+ // Device is not dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(false)
+ communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+ // Scene stays as Communal
+ advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+ val scene by collectLastValue(communalInteractor.desiredScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ }
+ }
+
+ @Test
+ fun hubTimeout_dreamStopped_staysOnCommunal() =
+ with(kosmos) {
+ testScope.runTest {
+ // Device is dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(true)
+ communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+ val scene by collectLastValue(communalInteractor.desiredScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Wait a bit, but not long enough to timeout.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Dream stops, timeout is cancelled and device stays on hub, because the regular
+ // screen timeout will take effect at this point.
+ fakeKeyguardRepository.setDreaming(false)
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ }
+ }
+
+ @Test
+ fun hubTimeout_dreamStartedHalfway_goesToCommunal() =
+ with(kosmos) {
+ testScope.runTest {
+ // Device is on communal, but not dreaming.
+ fakeKeyguardRepository.setDreaming(false)
+ communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+ val scene by collectLastValue(communalInteractor.desiredScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Wait a bit, but not long enough to timeout, then start dreaming.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ fakeKeyguardRepository.setDreaming(true)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Device times out after one screen timeout interval, dream doesn't reset timeout.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
+ }
+ }
+
+ @Test
+ fun hubTimeout_userActivityTriggered_resetsTimeout() =
+ with(kosmos) {
+ testScope.runTest {
+ // Device is dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(true)
+ communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+ val scene by collectLastValue(communalInteractor.desiredScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Wait a bit, but not long enough to timeout.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+
+ // Send user interaction to reset timeout.
+ communalInteractor.signalUserInteraction()
+
+ // If user activity didn't reset timeout, we would have gone back to Blank by now.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Timeout happens one interval after the user interaction.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
+ }
+ }
+
+ @Test
+ fun hubTimeout_screenTimeoutChanged() =
+ with(kosmos) {
+ testScope.runTest {
+ fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2)
+
+ // Device is dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(true)
+ communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+ val scene by collectLastValue(communalInteractor.desiredScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Scene times out back to blank after the screen timeout.
+ advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
+ }
+ }
+
private fun TestScope.updateDocked(docked: Boolean) =
with(kosmos) {
runCurrent()
@@ -260,4 +395,8 @@
setCommunalAvailable(true)
runCurrent()
}
+
+ companion object {
+ private const val SCREEN_TIMEOUT = 1000
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 24c651f3..a9541d9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -61,6 +61,7 @@
private val testScope = kosmos.testScope
private val repository by lazy { kosmos.fakeKeyguardRepository }
private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val fromGoneTransitionInteractor by lazy { kosmos.fromGoneTransitionInteractor }
private val commandQueue by lazy { FakeCommandQueue() }
private val bouncerRepository = FakeKeyguardBouncerRepository()
private val shadeRepository = FakeShadeRepository()
@@ -79,6 +80,7 @@
shadeRepository = shadeRepository,
keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
sceneInteractorProvider = { sceneInteractor },
+ fromGoneTransitionInteractor = { fromGoneTransitionInteractor },
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index cd4db2f..769caaa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -47,9 +47,7 @@
@kotlinx.coroutines.ExperimentalCoroutinesApi
@android.platform.test.annotations.EnabledOnRavenwood
class KeyguardTransitionInteractorTest : SysuiTestCase() {
-
val kosmos = testKosmos()
-
val underTest = kosmos.keyguardTransitionInteractor
val repository = kosmos.fakeKeyguardTransitionRepository
val testScope = kosmos.testScope
@@ -242,34 +240,35 @@
}
@Test
- fun transitionValue() = runTest {
- val startedSteps by collectValues(underTest.transitionValue(state = DOZING))
+ fun transitionValue() =
+ testScope.runTest {
+ val startedSteps by collectValues(underTest.transitionValue(state = DOZING))
- val toSteps =
- listOf(
- TransitionStep(AOD, DOZING, 0f, STARTED),
- TransitionStep(AOD, DOZING, 0.5f, RUNNING),
- TransitionStep(AOD, DOZING, 1f, FINISHED),
- )
- toSteps.forEach {
- repository.sendTransitionStep(it)
- runCurrent()
+ val toSteps =
+ listOf(
+ TransitionStep(AOD, DOZING, 0f, STARTED),
+ TransitionStep(AOD, DOZING, 0.5f, RUNNING),
+ TransitionStep(AOD, DOZING, 1f, FINISHED),
+ )
+ toSteps.forEach {
+ repository.sendTransitionStep(it)
+ runCurrent()
+ }
+
+ val fromSteps =
+ listOf(
+ TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
+ TransitionStep(DOZING, LOCKSCREEN, 0.5f, RUNNING),
+ TransitionStep(DOZING, LOCKSCREEN, 1f, FINISHED),
+ )
+ fromSteps.forEach {
+ repository.sendTransitionStep(it)
+ runCurrent()
+ }
+
+ assertThat(startedSteps).isEqualTo(listOf(0f, 0.5f, 1f, 1f, 0.5f, 0f))
}
- val fromSteps =
- listOf(
- TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
- TransitionStep(DOZING, LOCKSCREEN, 0.5f, RUNNING),
- TransitionStep(DOZING, LOCKSCREEN, 1f, FINISHED),
- )
- fromSteps.forEach {
- repository.sendTransitionStep(it)
- runCurrent()
- }
-
- assertThat(startedSteps).isEqualTo(listOf(0f, 0.5f, 1f, 1f, 0.5f, 0f))
- }
-
@Test
fun isInTransitionToAnyState() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
index de659cf..f517cec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
@@ -81,12 +81,12 @@
}
@Test
- fun movement_initializedToZero() =
+ fun movement_initializedToDefaultValues() =
testScope.runTest {
val movement by collectLastValue(underTest.movement(burnInParameters))
assertThat(movement?.translationY).isEqualTo(0)
assertThat(movement?.translationX).isEqualTo(0)
- assertThat(movement?.scale).isEqualTo(0f)
+ assertThat(movement?.scale).isEqualTo(1f)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt
index 64125f1..af96780 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt
@@ -99,6 +99,31 @@
values.forEach { assertThat(it.value).isIn(Range.closed(-100f, 0f)) }
}
+ @Test
+ fun lockscreenTranslationX_resetsAfterCancellation() =
+ testScope.runTest {
+ configurationRepository.setDimensionPixelSize(
+ R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x,
+ 100
+ )
+ val values by collectValues(underTest.keyguardTranslationX)
+ assertThat(values).isEmpty()
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ step(0f, TransitionState.STARTED),
+ step(0.3f),
+ step(0.5f),
+ step(0.9f, TransitionState.CANCELED)
+ ),
+ testScope,
+ )
+
+ assertThat(values).hasSize(4)
+ values.forEach { assertThat(it.value).isIn(Range.closed(-100f, 0f)) }
+ assertThat(values.last().value).isEqualTo(0f)
+ }
+
private fun step(
value: Float,
state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 979d504..fc604aa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -86,6 +86,13 @@
}
@Test
+ fun defaultBurnInScaleEqualsOne() =
+ testScope.runTest {
+ val burnInScale by collectLastValue(underTest.scale)
+ assertThat(burnInScale!!.scale).isEqualTo(1f)
+ }
+
+ @Test
fun burnInLayerVisibility() =
testScope.runTest {
val burnInLayerVisibility by collectLastValue(underTest.burnInLayerVisibility)
@@ -250,6 +257,17 @@
keyguardRepository.topClippingBounds.value = 1000
assertThat(topClippingBounds).isEqualTo(1000)
+
+ // Run at least 1 transition to make sure value remains at 0
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+
+ // Make sure the value hasn't changed since we're GONE
+ keyguardRepository.topClippingBounds.value = 5
+ assertThat(topClippingBounds).isEqualTo(1000)
}
@Test
@@ -330,6 +348,48 @@
}
@Test
+ fun alpha_shadeClosedOverLockscreen_isOne() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.alpha(viewState))
+
+ // Transition to the lockscreen.
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ testScope,
+ )
+
+ // Open the shade.
+ shadeRepository.setQsExpansion(1f)
+ assertThat(alpha).isEqualTo(0f)
+
+ // Close the shade, alpha returns to 1.
+ shadeRepository.setQsExpansion(0f)
+ assertThat(alpha).isEqualTo(1f)
+ }
+
+ @Test
+ fun alpha_shadeClosedOverDream_isZero() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.alpha(viewState))
+
+ // Transition to dreaming.
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ testScope,
+ )
+
+ // Open the shade.
+ shadeRepository.setQsExpansion(1f)
+ assertThat(alpha).isEqualTo(0f)
+
+ // Close the shade, alpha is still 0 since we're not on the lockscreen.
+ shadeRepository.setQsExpansion(0f)
+ assertThat(alpha).isEqualTo(0f)
+ }
+
+ @Test
fun alpha_idleOnOccluded_isZero() =
testScope.runTest {
val alpha by collectLastValue(underTest.alpha(viewState))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index e683f34c..53a8e5d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -26,6 +26,7 @@
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -38,6 +39,7 @@
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
+import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel
import com.android.systemui.kosmos.testScope
@@ -699,41 +701,57 @@
}
@Test
+ fun alphaOnFullQsExpansion() =
+ testScope.runTest {
+ val viewState = ViewStateAccessor()
+ val alpha by collectLastValue(underTest.keyguardAlpha(viewState))
+
+ showLockscreenWithQSExpanded()
+
+ // Alpha fades out as QS expands
+ shadeRepository.setQsExpansion(0.5f)
+ assertThat(alpha).isWithin(0.01f).of(0.5f)
+ shadeRepository.setQsExpansion(0.9f)
+ assertThat(alpha).isWithin(0.01f).of(0.1f)
+
+ // Ensure that alpha is set back to 1f when QS is fully expanded
+ shadeRepository.setQsExpansion(1f)
+ assertThat(alpha).isEqualTo(1f)
+ }
+
+ @Test
fun shadeCollapseFadeIn() =
testScope.runTest {
- val fadeIn by collectLastValue(underTest.shadeCollapseFadeIn)
+ val fadeIn by collectValues(underTest.shadeCollapseFadeIn)
// Start on lockscreen without the shade
- underTest.setShadeCollapseFadeInComplete(false)
showLockscreen()
- assertThat(fadeIn).isEqualTo(false)
+ assertThat(fadeIn[0]).isEqualTo(false)
// ... then the shade expands
showLockscreenWithShadeExpanded()
- assertThat(fadeIn).isEqualTo(false)
+ assertThat(fadeIn[0]).isEqualTo(false)
// ... it collapses
showLockscreen()
- assertThat(fadeIn).isEqualTo(true)
+ assertThat(fadeIn[1]).isEqualTo(true)
- // ... now send animation complete signal
- underTest.setShadeCollapseFadeInComplete(true)
- assertThat(fadeIn).isEqualTo(false)
+ // ... and ensure the value goes back to false
+ assertThat(fadeIn[2]).isEqualTo(false)
}
@Test
fun shadeCollapseFadeIn_doesNotRunIfTransitioningToAod() =
testScope.runTest {
- val fadeIn by collectLastValue(underTest.shadeCollapseFadeIn)
+ val fadeIn by collectValues(underTest.shadeCollapseFadeIn)
// Start on lockscreen without the shade
- underTest.setShadeCollapseFadeInComplete(false)
showLockscreen()
- assertThat(fadeIn).isEqualTo(false)
+ assertThat(fadeIn[0]).isEqualTo(false)
// ... then the shade expands
showLockscreenWithShadeExpanded()
- assertThat(fadeIn).isEqualTo(false)
+ assertThat(fadeIn[0]).isEqualTo(false)
// ... then user hits power to go to AOD
keyguardTransitionRepository.sendTransitionSteps(
@@ -744,7 +762,7 @@
// ... followed by a shade collapse
showLockscreen()
// ... does not trigger a fade in
- assertThat(fadeIn).isEqualTo(false)
+ assertThat(fadeIn[0]).isEqualTo(false)
}
private suspend fun TestScope.showLockscreen() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
new file mode 100644
index 0000000..be63301
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -0,0 +1,312 @@
+/*
+ * 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.policy
+
+import android.app.Notification
+import android.platform.test.annotations.EnableFlags
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
+import com.android.systemui.statusbar.policy.HeadsUpManagerTestUtil.createFullScreenIntentEntry
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.settings.FakeGlobalSettings
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.invocation.InvocationOnMock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@SmallTest
+@RunWithLooper
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(NotificationThrottleHun.FLAG_NAME)
+class AvalancheControllerTest : SysuiTestCase() {
+
+ private val mAvalancheController = AvalancheController()
+
+ // For creating mocks
+ @get:Rule var rule: MockitoRule = MockitoJUnit.rule()
+ @Mock private val runnableMock: Runnable? = null
+
+ // For creating TestableHeadsUpManager
+ @Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
+ private val mUiEventLoggerFake = UiEventLoggerFake()
+ private val mLogger = Mockito.spy(HeadsUpManagerLogger(logcatLogBuffer()))
+ private val mGlobalSettings = FakeGlobalSettings()
+ private val mSystemClock = FakeSystemClock()
+ private val mExecutor = FakeExecutor(mSystemClock)
+ private var testableHeadsUpManager: BaseHeadsUpManager? = null
+
+ @Before
+ fun setUp() {
+ // Use default non-a11y timeout
+ Mockito.`when`(
+ mAccessibilityMgr!!.getRecommendedTimeoutMillis(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt()
+ )
+ )
+ .then { i: InvocationOnMock -> i.getArgument(0) }
+
+ // Initialize TestableHeadsUpManager here instead of at declaration, when mocks will be null
+ testableHeadsUpManager =
+ TestableHeadsUpManager(
+ mContext,
+ mLogger,
+ mExecutor,
+ mGlobalSettings,
+ mSystemClock,
+ mAccessibilityMgr,
+ mUiEventLoggerFake,
+ mAvalancheController
+ )
+ }
+
+ private fun createHeadsUpEntry(id: Int): BaseHeadsUpManager.HeadsUpEntry {
+ val entry = testableHeadsUpManager!!.createHeadsUpEntry()
+
+ entry.setEntry(
+ NotificationEntryBuilder()
+ .setSbn(HeadsUpManagerTestUtil.createSbn(id, Notification.Builder(mContext, "")))
+ .build()
+ )
+ return entry
+ }
+
+ private fun createFsiHeadsUpEntry(id: Int): BaseHeadsUpManager.HeadsUpEntry {
+ val entry = testableHeadsUpManager!!.createHeadsUpEntry()
+ entry.setEntry(createFullScreenIntentEntry(id, mContext))
+ return entry
+ }
+
+ @Test
+ fun testUpdate_isShowing_runsRunnable() {
+ // Entry is showing
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = headsUpEntry
+
+ // Update
+ mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Runnable was run
+ Mockito.verify(runnableMock, Mockito.times(1)).run()
+ }
+
+ @Test
+ fun testUpdate_noneShowingAndNotNext_showNow() {
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+
+ // None showing
+ mAvalancheController.headsUpEntryShowing = null
+
+ // Entry is NOT next
+ mAvalancheController.clearNext()
+
+ // Update
+ mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Entry is showing now
+ Truth.assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(headsUpEntry)
+ }
+
+ @Test
+ fun testUpdate_isNext_addsRunnable() {
+ // Another entry is already showing
+ val otherShowingEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = otherShowingEntry
+
+ // Entry is next
+ val headsUpEntry = createHeadsUpEntry(id = 1)
+ mAvalancheController.addToNext(headsUpEntry, runnableMock!!)
+
+ // Entry has one Runnable
+ val runnableList: List<Runnable?>? = mAvalancheController.nextMap[headsUpEntry]
+ Truth.assertThat(runnableList).isNotNull()
+ Truth.assertThat(runnableList!!.size).isEqualTo(1)
+
+ // Update
+ mAvalancheController.update(headsUpEntry, runnableMock, "testLabel")
+
+ // Entry has two Runnables
+ Truth.assertThat(runnableList.size).isEqualTo(2)
+ }
+
+ @Test
+ fun testUpdate_isNotNextWithOtherHunShowing_isNext() {
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+
+ // Another entry is already showing
+ val otherShowingEntry = createHeadsUpEntry(id = 1)
+ mAvalancheController.headsUpEntryShowing = otherShowingEntry
+
+ // Entry is NOT next
+ mAvalancheController.clearNext()
+
+ // Update
+ mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Entry is next
+ Truth.assertThat(mAvalancheController.nextMap.containsKey(headsUpEntry)).isTrue()
+ }
+
+ @Test
+ fun testDelete_isNext_removedFromNext_runnableNotRun() {
+ // Entry is next
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.addToNext(headsUpEntry, runnableMock!!)
+
+ // Delete
+ mAvalancheController.delete(headsUpEntry, runnableMock, "testLabel")
+
+ // Entry was removed from next
+ Truth.assertThat(mAvalancheController.nextMap.containsKey(headsUpEntry)).isFalse()
+
+ // Runnable was not run
+ Mockito.verify(runnableMock, Mockito.times(0)).run()
+ }
+
+ @Test
+ fun testDelete_wasDropped_removedFromDropSet() {
+ // Entry was dropped
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.debugDropSet.add(headsUpEntry)
+
+ // Delete
+ mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Entry was removed from dropSet
+ Truth.assertThat(mAvalancheController.debugDropSet.contains(headsUpEntry)).isFalse()
+ }
+
+ @Test
+ fun testDelete_wasDropped_runnableNotRun() {
+ // Entry was dropped
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.debugDropSet.add(headsUpEntry)
+
+ // Delete
+ mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Runnable was not run
+ Mockito.verify(runnableMock, Mockito.times(0)).run()
+ }
+
+ @Test
+ fun testDelete_isShowing_runnableRun() {
+ // Entry is showing
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = headsUpEntry
+
+ // Delete
+ mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Runnable was run
+ Mockito.verify(runnableMock, Mockito.times(1)).run()
+ }
+
+ @Test
+ fun testDelete_isShowing_showNext() {
+ // Entry is showing
+ val showingEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = showingEntry
+
+ // There's another entry waiting to show next
+ val nextEntry = createHeadsUpEntry(id = 1)
+ mAvalancheController.addToNext(nextEntry, runnableMock!!)
+
+ // Delete
+ mAvalancheController.delete(showingEntry, runnableMock, "testLabel")
+
+ // Next entry is shown
+ Truth.assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(nextEntry)
+ }
+
+ @Test
+ fun testGetDurationMs_lastEntry_useAutoDismissTime() {
+ // Entry is showing
+ val showingEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = showingEntry
+
+ // Nothing is next
+ mAvalancheController.clearNext()
+
+ val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
+ Truth.assertThat(durationMs).isEqualTo(5000)
+ }
+
+ @Test
+ fun testGetDurationMs_nextEntryLowerPriority_500() {
+ // Entry is showing
+ val showingEntry = createFsiHeadsUpEntry(id = 1)
+ mAvalancheController.headsUpEntryShowing = showingEntry
+
+ // There's another entry waiting to show next
+ val nextEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.addToNext(nextEntry, runnableMock!!)
+
+ // Next entry has lower priority
+ Truth.assertThat(nextEntry.compareNonTimeFields(showingEntry)).isEqualTo(1)
+
+ val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
+ Truth.assertThat(durationMs).isEqualTo(5000)
+ }
+
+ @Test
+ fun testGetDurationMs_nextEntrySamePriority_1000() {
+ // Entry is showing
+ val showingEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = showingEntry
+
+ // There's another entry waiting to show next
+ val nextEntry = createHeadsUpEntry(id = 1)
+ mAvalancheController.addToNext(nextEntry, runnableMock!!)
+
+ // Same priority
+ Truth.assertThat(nextEntry.compareNonTimeFields(showingEntry)).isEqualTo(0)
+
+ val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
+ Truth.assertThat(durationMs).isEqualTo(1000)
+ }
+
+ @Test
+ fun testGetDurationMs_nextEntryHigherPriority_500() {
+ // Entry is showing
+ val showingEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = showingEntry
+
+ // There's another entry waiting to show next
+ val nextEntry = createFsiHeadsUpEntry(id = 1)
+ mAvalancheController.addToNext(nextEntry, runnableMock!!)
+
+ // Next entry has higher priority
+ Truth.assertThat(nextEntry.compareNonTimeFields(showingEntry)).isEqualTo(-1)
+
+ val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
+ Truth.assertThat(durationMs).isEqualTo(500)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index db4d42f..ed0d272 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -35,13 +35,10 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.ActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Person;
import android.content.Intent;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
import android.testing.TestableLooper;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -77,6 +74,8 @@
private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
+ private AvalancheController mAvalancheController = new AvalancheController();
+
@Mock private AccessibilityManagerWrapper mAccessibilityMgr;
protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
@@ -99,7 +98,7 @@
private BaseHeadsUpManager createHeadsUpManager() {
return new TestableHeadsUpManager(mContext, mLogger, mExecutor, mGlobalSettings,
- mSystemClock, mAccessibilityMgr, mUiEventLoggerFake);
+ mSystemClock, mAccessibilityMgr, mUiEventLoggerFake, mAvalancheController);
}
private NotificationEntry createStickyEntry(int id) {
@@ -118,21 +117,6 @@
return HeadsUpManagerTestUtil.createEntry(id, notif);
}
- private PendingIntent createFullScreenIntent() {
- return PendingIntent.getActivity(
- getContext(), 0, new Intent(getContext(), this.getClass()),
- PendingIntent.FLAG_MUTABLE_UNAUDITED);
- }
-
- private NotificationEntry createFullScreenIntentEntry(int id) {
- final Notification notif = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .setFullScreenIntent(createFullScreenIntent(), /* highPriority */ true)
- .build();
- return HeadsUpManagerTestUtil.createEntry(id, notif);
- }
-
-
private void useAccessibilityTimeout(boolean use) {
if (use) {
doReturn(TEST_A11Y_AUTO_DISMISS_TIME).when(mAccessibilityMgr)
@@ -240,7 +224,8 @@
@Test
public void testShouldHeadsUpBecomePinned_hasFSI_notUnpinned_true() {
final BaseHeadsUpManager hum = createHeadsUpManager();
- final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0);
+ final NotificationEntry notifEntry =
+ HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext);
// Add notifEntry to ANM mAlertEntries map and make it NOT unpinned
hum.showNotification(notifEntry);
@@ -255,7 +240,8 @@
@Test
public void testShouldHeadsUpBecomePinned_wasUnpinned_false() {
final BaseHeadsUpManager hum = createHeadsUpManager();
- final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0);
+ final NotificationEntry notifEntry =
+ HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext);
// Add notifEntry to ANM mAlertEntries map and make it unpinned
hum.showNotification(notifEntry);
@@ -444,7 +430,8 @@
@Test
public void testIsSticky_hasFullScreenIntent_true() {
final BaseHeadsUpManager hum = createHeadsUpManager();
- final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0);
+ final NotificationEntry notifEntry =
+ HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext);
hum.showNotification(notifEntry);
@@ -555,7 +542,8 @@
// Needs full screen intent in order to be pinned
final BaseHeadsUpManager.HeadsUpEntry entryToPin = hum.new HeadsUpEntry();
- entryToPin.setEntry(createFullScreenIntentEntry(/* id = */ 0));
+ entryToPin.setEntry(
+ HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext));
// Note: the standard way to show a notification would be calling showNotification rather
// than onAlertEntryAdded. However, in practice showNotification in effect adds
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
index c032d7c..61a79d8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
@@ -28,8 +28,8 @@
import android.content.Context;
import android.testing.TestableLooper;
-import androidx.test.filters.SmallTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -76,6 +76,7 @@
@Mock private UiEventLogger mUiEventLogger;
@Mock private JavaAdapter mJavaAdapter;
@Mock private ShadeInteractor mShadeInteractor;
+ private AvalancheController mAvalancheController = new AvalancheController();
private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
TestableHeadsUpManagerPhone(
@@ -92,7 +93,8 @@
AccessibilityManagerWrapper accessibilityManagerWrapper,
UiEventLogger uiEventLogger,
JavaAdapter javaAdapter,
- ShadeInteractor shadeInteractor
+ ShadeInteractor shadeInteractor,
+ AvalancheController avalancheController
) {
super(
context,
@@ -109,7 +111,8 @@
accessibilityManagerWrapper,
uiEventLogger,
javaAdapter,
- shadeInteractor
+ shadeInteractor,
+ avalancheController
);
mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
@@ -131,12 +134,15 @@
mAccessibilityManagerWrapper,
mUiEventLogger,
mJavaAdapter,
- mShadeInteractor
+ mShadeInteractor,
+ mAvalancheController
);
}
@Before
public void setUp() {
+ // TODO(b/315362456) create separate test with the flag disabled
+ // then modify this file to test with the flag enabled
mSetFlagsRule.disableFlags(NotificationThrottleHun.FLAG_NAME);
when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false));
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java
index c70b03b..bda8619 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.policy;
import android.app.ActivityManager;
+import android.app.PendingIntent;
+import android.content.Intent;
import android.os.UserHandle;
import android.content.Context;
@@ -67,4 +69,16 @@
return new NotificationEntryBuilder().setSbn(
HeadsUpManagerTestUtil.createSbn(id, context)).build();
}
+
+ protected static NotificationEntry createFullScreenIntentEntry(int id, Context context) {
+ final PendingIntent intent = PendingIntent.getActivity(
+ context, 0, new Intent(),
+ PendingIntent.FLAG_IMMUTABLE);
+
+ final Notification notif = new Notification.Builder(context, "")
+ .setSmallIcon(com.android.systemui.res.R.drawable.ic_person)
+ .setFullScreenIntent(intent, /* highPriority */ true)
+ .build();
+ return HeadsUpManagerTestUtil.createEntry(id, notif);
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
index 2747629..d8f77f0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
@@ -43,9 +43,10 @@
GlobalSettings globalSettings,
SystemClock systemClock,
AccessibilityManagerWrapper accessibilityManagerWrapper,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ AvalancheController avalancheController) {
super(context, logger, mockExecutorHandler(executor), globalSettings, systemClock,
- executor, accessibilityManagerWrapper, uiEventLogger);
+ executor, accessibilityManagerWrapper, uiEventLogger, avalancheController);
mTouchAcceptanceDelay = BaseHeadsUpManagerTest.TEST_TOUCH_ACCEPTANCE_TIME;
mMinimumDisplayTime = BaseHeadsUpManagerTest.TEST_MINIMUM_DISPLAY_TIME;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
index e06efe8..3d93654 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
@@ -135,7 +135,7 @@
}
@Test
- fun streamIsMuted_getStream_volumeZero() {
+ fun streamIsMuted_getStream_volumeMin() {
with(kosmos) {
testScope.runTest {
val model by collectLastValue(underTest.getAudioStream(audioStream))
@@ -166,7 +166,7 @@
}
@Test
- fun ringerModeVibrateAndMuted_getNotificationStream_volumeIsZero() {
+ fun ringerModeVibrateAndMuted_getNotificationStream_volumeIsMin() {
with(kosmos) {
testScope.runTest {
audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_VIBRATE))
@@ -184,7 +184,7 @@
}
@Test
- fun ringerModeVibrate_getRingerStream_volumeIsZero() {
+ fun ringerModeVibrate_getRingerStream_volumeIsMin() {
with(kosmos) {
testScope.runTest {
audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_VIBRATE))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
index 449e8bf..1ed7f5d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.volume.panel.component.spatial.domain
+import android.media.AudioDeviceAttributes
+import android.media.AudioDeviceInfo
import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.testing.TestableLooper.RunWithLooper
@@ -49,26 +51,27 @@
class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() {
private val kosmos = testKosmos()
- private val cachedBluetoothDevice: CachedBluetoothDevice = mock {
- whenever(address).thenReturn("test_address")
- }
- private val bluetoothMediaDevice: BluetoothMediaDevice = mock {
- whenever(cachedDevice).thenReturn(cachedBluetoothDevice)
- }
-
private lateinit var underTest: SpatialAudioAvailabilityCriteria
@Before
fun setup() {
with(kosmos) {
- mediaControllerRepository.setActiveLocalMediaController(
- mediaController.apply {
- whenever(packageName).thenReturn("test.pkg")
- whenever(sessionToken).thenReturn(MediaSession.Token(0, mock {}))
- whenever(playbackState).thenReturn(PlaybackState.Builder().build())
+ val cachedBluetoothDevice: CachedBluetoothDevice = mock {
+ whenever(address).thenReturn("test_address")
+ }
+ localMediaRepository.updateCurrentConnectedDevice(
+ mock<BluetoothMediaDevice> {
+ whenever(name).thenReturn("test_device")
+ whenever(cachedDevice).thenReturn(cachedBluetoothDevice)
}
)
+ whenever(mediaController.packageName).thenReturn("test.pkg")
+ whenever(mediaController.sessionToken).thenReturn(MediaSession.Token(0, mock {}))
+ whenever(mediaController.playbackState).thenReturn(PlaybackState.Builder().build())
+
+ mediaControllerRepository.setActiveLocalMediaController(mediaController)
+
underTest = SpatialAudioAvailabilityCriteria(spatialAudioComponentInteractor)
}
}
@@ -77,9 +80,8 @@
fun noSpatialAudio_noHeadTracking_unavailable() {
with(kosmos) {
testScope.runTest {
- localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
- spatializerRepository.defaultHeadTrackingAvailable = false
- spatializerRepository.defaultSpatialAudioAvailable = false
+ spatializerRepository.setIsSpatialAudioAvailable(headset, false)
+ spatializerRepository.setIsHeadTrackingAvailable(headset, false)
val isAvailable by collectLastValue(underTest.isAvailable())
runCurrent()
@@ -93,9 +95,8 @@
fun spatialAudio_noHeadTracking_available() {
with(kosmos) {
testScope.runTest {
- localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
- spatializerRepository.defaultHeadTrackingAvailable = false
- spatializerRepository.defaultSpatialAudioAvailable = true
+ spatializerRepository.setIsSpatialAudioAvailable(headset, true)
+ spatializerRepository.setIsHeadTrackingAvailable(headset, false)
val isAvailable by collectLastValue(underTest.isAvailable())
runCurrent()
@@ -109,9 +110,8 @@
fun spatialAudio_headTracking_available() {
with(kosmos) {
testScope.runTest {
- localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
- spatializerRepository.defaultHeadTrackingAvailable = true
- spatializerRepository.defaultSpatialAudioAvailable = true
+ spatializerRepository.setIsSpatialAudioAvailable(headset, true)
+ spatializerRepository.setIsHeadTrackingAvailable(headset, true)
val isAvailable by collectLastValue(underTest.isAvailable())
runCurrent()
@@ -125,8 +125,8 @@
fun spatialAudio_headTracking_noDevice_unavailable() {
with(kosmos) {
testScope.runTest {
- spatializerRepository.defaultHeadTrackingAvailable = true
- spatializerRepository.defaultSpatialAudioAvailable = true
+ localMediaRepository.updateCurrentConnectedDevice(null)
+ spatializerRepository.setIsSpatialAudioAvailable(headset, false)
val isAvailable by collectLastValue(underTest.isAvailable())
runCurrent()
@@ -135,4 +135,13 @@
}
}
}
+
+ private companion object {
+ val headset =
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_HEADSET,
+ "test_address"
+ )
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
index 06ae220..281b03d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
@@ -26,6 +26,7 @@
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.media.BluetoothMediaDevice
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.spatializerInteractor
@@ -37,6 +38,7 @@
import com.android.systemui.volume.mediaController
import com.android.systemui.volume.mediaControllerRepository
import com.android.systemui.volume.mediaOutputInteractor
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -74,16 +76,6 @@
mediaControllerRepository.setActiveLocalMediaController(mediaController)
- spatializerRepository.setIsSpatialAudioAvailable(
- AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_OUTPUT,
- AudioDeviceInfo.TYPE_BLE_HEADSET,
- "test_address"
- ),
- true
- )
- spatializerRepository.defaultHeadTrackingAvailable = true
-
underTest =
SpatialAudioComponentInteractor(
mediaOutputInteractor,
@@ -97,6 +89,7 @@
fun setEnabled_changesIsEnabled() {
with(kosmos) {
testScope.runTest {
+ spatializerRepository.setIsSpatialAudioAvailable(headset, true)
val values by collectValues(underTest.isEnabled)
underTest.setEnabled(SpatialAudioEnabledModel.Disabled)
@@ -108,6 +101,7 @@
assertThat(values)
.containsExactly(
+ SpatialAudioEnabledModel.Unknown,
SpatialAudioEnabledModel.Disabled,
SpatialAudioEnabledModel.HeadTrackingEnabled,
SpatialAudioEnabledModel.SpatialAudioEnabled,
@@ -116,4 +110,92 @@
}
}
}
+
+ @Test
+ fun connectedDeviceSupports_isAvailable_SpatialAudio() {
+ with(kosmos) {
+ testScope.runTest {
+ spatializerRepository.setIsSpatialAudioAvailable(headset, true)
+
+ val isAvailable by collectLastValue(underTest.isAvailable)
+
+ assertThat(isAvailable)
+ .isInstanceOf(SpatialAudioAvailabilityModel.SpatialAudio::class.java)
+ }
+ }
+ }
+
+ @Test
+ fun connectedDeviceSupportsHeadTracking_isAvailable_HeadTracking() {
+ with(kosmos) {
+ testScope.runTest {
+ spatializerRepository.setIsSpatialAudioAvailable(headset, true)
+ spatializerRepository.setIsHeadTrackingAvailable(headset, true)
+
+ val isAvailable by collectLastValue(underTest.isAvailable)
+
+ assertThat(isAvailable)
+ .isInstanceOf(SpatialAudioAvailabilityModel.HeadTracking::class.java)
+ }
+ }
+ }
+
+ @Test
+ fun connectedDeviceDoesntSupport_isAvailable_Unavailable() {
+ with(kosmos) {
+ testScope.runTest {
+ spatializerRepository.setIsSpatialAudioAvailable(headset, false)
+
+ val isAvailable by collectLastValue(underTest.isAvailable)
+
+ assertThat(isAvailable)
+ .isInstanceOf(SpatialAudioAvailabilityModel.Unavailable::class.java)
+ }
+ }
+ }
+
+ @Test
+ fun noConnectedDeviceBuiltinSupports_isAvailable_SpatialAudio() {
+ with(kosmos) {
+ testScope.runTest {
+ localMediaRepository.updateCurrentConnectedDevice(null)
+ spatializerRepository.setIsSpatialAudioAvailable(builtinSpeaker, true)
+
+ val isAvailable by collectLastValue(underTest.isAvailable)
+
+ assertThat(isAvailable)
+ .isInstanceOf(SpatialAudioAvailabilityModel.SpatialAudio::class.java)
+ }
+ }
+ }
+
+ @Test
+ fun noConnectedDeviceBuiltinDoesntSupport_isAvailable_Unavailable() {
+ with(kosmos) {
+ testScope.runTest {
+ localMediaRepository.updateCurrentConnectedDevice(null)
+ spatializerRepository.setIsSpatialAudioAvailable(builtinSpeaker, false)
+
+ val isAvailable by collectLastValue(underTest.isAvailable)
+
+ assertThat(isAvailable)
+ .isInstanceOf(SpatialAudioAvailabilityModel.Unavailable::class.java)
+ }
+ }
+ }
+
+ private companion object {
+ val headset =
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_HEADSET,
+ "test_address"
+ )
+ val builtinSpeaker =
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+ ""
+ )
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt
index a1e4fca..79d3fe9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt
@@ -30,28 +30,8 @@
private val underTest = VolumeSliderInteractor()
@Test
- fun translateValueToVolume() {
- assertThat(underTest.translateValueToVolume(30f, volumeRange)).isEqualTo(3)
- }
-
- @Test
- fun processVolumeToValue_muted_zero() {
- assertThat(underTest.processVolumeToValue(3, volumeRange, null, true)).isEqualTo(0)
- }
-
- @Test
- fun processVolumeToValue_currentValue_currentValue() {
- assertThat(underTest.processVolumeToValue(3, volumeRange, 30f, false)).isEqualTo(30f)
- }
-
- @Test
- fun processVolumeToValue_currentValueDiffersVolume_returnsTranslatedVolume() {
- assertThat(underTest.processVolumeToValue(1, volumeRange, 60f, false)).isEqualTo(10f)
- }
-
- @Test
- fun processVolumeToValue_currentValueDiffersNotEnoughVolume_returnsTranslatedVolume() {
- assertThat(underTest.processVolumeToValue(1, volumeRange, 12f, false)).isEqualTo(12f)
+ fun processVolumeToValue_returnsTranslatedVolume() {
+ assertThat(underTest.processVolumeToValue(2, volumeRange)).isEqualTo(20f)
}
private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
index 71866b3..82ce6d7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
@@ -37,7 +37,7 @@
DefaultComponentsLayoutManager(
BOTTOM_BAR,
headerComponents = listOf(COMPONENT_1),
- footerComponents = listOf(COMPONENT_2),
+ footerComponents = listOf(COMPONENT_5, COMPONENT_2),
)
@Test
@@ -48,10 +48,18 @@
val component2 = ComponentState(COMPONENT_2, kosmos.mockVolumePanelUiComponent, false)
val component3 = ComponentState(COMPONENT_3, kosmos.mockVolumePanelUiComponent, false)
val component4 = ComponentState(COMPONENT_4, kosmos.mockVolumePanelUiComponent, false)
+ val component5 = ComponentState(COMPONENT_5, kosmos.mockVolumePanelUiComponent, false)
val layout =
underTest.layout(
VolumePanelState(0, false, false),
- setOf(bottomBarComponentState, component1, component2, component3, component4)
+ setOf(
+ bottomBarComponentState,
+ component1,
+ component2,
+ component3,
+ component4,
+ component5,
+ )
)
Truth.assertThat(layout.bottomBarComponent).isEqualTo(bottomBarComponentState)
@@ -59,7 +67,7 @@
.containsExactlyElementsIn(listOf(component1))
.inOrder()
Truth.assertThat(layout.footerComponents)
- .containsExactlyElementsIn(listOf(component2))
+ .containsExactlyElementsIn(listOf(component5, component2))
.inOrder()
Truth.assertThat(layout.contentComponents)
.containsExactlyElementsIn(listOf(component3, component4))
@@ -85,5 +93,6 @@
const val COMPONENT_2: VolumePanelComponentKey = "test_component:2"
const val COMPONENT_3: VolumePanelComponentKey = "test_component:3"
const val COMPONENT_4: VolumePanelComponentKey = "test_component:4"
+ const val COMPONENT_5: VolumePanelComponentKey = "test_component:5"
}
}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
index b562d7b..6780e57 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
@@ -85,6 +85,7 @@
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
+ android:focusable="false"
androidprv:layout_constraintVertical_bias="1.0"
androidprv:layout_constraintDimensionRatio="1.0"
androidprv:layout_constraintStart_toStartOf="parent"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
index d9011c2..d991581 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
@@ -57,6 +57,7 @@
android:id="@+id/lockPatternView"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:focusable="false"
androidprv:layout_constraintTop_toBottomOf="@id/pattern_top_guideline"
androidprv:layout_constraintBottom_toBottomOf="parent"
androidprv:layout_constraintLeft_toLeftOf="parent"
diff --git a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
index 5fe74aa..2d63c8d 100644
--- a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
@@ -26,7 +26,7 @@
android:paddingHorizontal="16dp"
android:paddingVertical="16dp"
android:visibility="visible"
- app:layout_constraintBottom_toTopOf="@+id/bottomGuideline"
+ app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/rightGuideline"
app:layout_constraintStart_toStartOf="@+id/leftGuideline"
app:layout_constraintTop_toTopOf="@+id/topGuideline" />
@@ -59,7 +59,7 @@
android:layout_width="0dp"
android:layout_height="0dp"
android:fillViewport="true"
- android:padding="16dp"
+ android:padding="24dp"
app:layout_constrainedHeight="true"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/buttonBarrier"
@@ -82,20 +82,20 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
- <LinearLayout
- android:id="@+id/customized_view_container"
- android:layout_width="wrap_content"
+ <TextView
+ android:id="@+id/logo_description"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
- android:gravity="center_vertical"
- android:orientation="vertical"
- android:paddingHorizontal="0dp"
- android:visibility="gone"
- app:layout_constraintBottom_toBottomOf="parent"
+ android:ellipsize="marquee"
+ android:gravity="@integer/biometric_dialog_text_gravity"
+ android:marqueeRepeatLimit="1"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:paddingLeft="8dp"
+ app:layout_constraintBottom_toBottomOf="@+id/logo"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_bias="0.0"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/subtitle"
- app:layout_constraintVertical_bias="0.0" />
+ app:layout_constraintStart_toEndOf="@+id/logo"
+ app:layout_constraintTop_toTopOf="@+id/logo" />
<Space
android:id="@+id/space_above_content"
@@ -108,6 +108,7 @@
style="@style/TextAppearance.AuthCredential.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginTop="12dp"
android:gravity="@integer/biometric_dialog_text_gravity"
android:paddingHorizontal="0dp"
android:textAlignment="viewStart"
@@ -124,6 +125,7 @@
style="@style/TextAppearance.AuthCredential.Subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginTop="12dp"
android:gravity="@integer/biometric_dialog_text_gravity"
android:paddingHorizontal="0dp"
android:textAlignment="viewStart"
@@ -133,6 +135,21 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
+ <LinearLayout
+ android:id="@+id/customized_view_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:orientation="vertical"
+ android:paddingHorizontal="0dp"
+ android:visibility="gone"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="0.0"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/subtitle"
+ app:layout_constraintVertical_bias="0.0" />
+
<TextView
android:id="@+id/description"
style="@style/TextAppearance.AuthCredential.Description"
@@ -148,20 +165,6 @@
app:layout_constraintTop_toBottomOf="@+id/subtitle"
app:layout_constraintVertical_bias="0.0" />
- <TextView
- android:id="@+id/logo_description"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:ellipsize="marquee"
- android:gravity="@integer/biometric_dialog_text_gravity"
- android:marqueeRepeatLimit="1"
- android:singleLine="true"
- android:textAlignment="viewStart"
- android:paddingLeft="8dp"
- app:layout_constraintBottom_toBottomOf="@+id/logo"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toEndOf="@+id/logo"
- app:layout_constraintTop_toTopOf="@+id/logo" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/contentBarrier"
@@ -178,7 +181,7 @@
android:id="@+id/indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
+ android:layout_marginTop="24dp"
android:accessibilityLiveRegion="polite"
android:fadingEdge="horizontal"
android:gravity="center_horizontal"
@@ -192,80 +195,15 @@
app:layout_constraintTop_toBottomOf="@+id/biometric_icon"
app:layout_constraintVertical_bias="0.0" />
- <!-- Negative Button, reserved for app -->
- <Button
- android:id="@+id/button_negative"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginBottom="8dp"
- android:layout_marginLeft="8dp"
- android:ellipsize="end"
- android:maxLines="2"
- android:visibility="invisible"
- app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline"
- app:layout_constraintStart_toStartOf="@+id/scrollView" />
-
- <!-- Cancel Button, replaces negative button when biometric is accepted -->
- <Button
- android:id="@+id/button_cancel"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginBottom="8dp"
- android:layout_marginLeft="8dp"
- android:text="@string/cancel"
- android:visibility="invisible"
- app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline"
- app:layout_constraintStart_toStartOf="@+id/scrollView" />
-
- <!-- "Use Credential" Button, replaces if device credential is allowed -->
- <Button
- android:id="@+id/button_use_credential"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginBottom="8dp"
- android:layout_marginLeft="8dp"
- android:visibility="invisible"
- app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline"
- app:layout_constraintStart_toStartOf="@+id/scrollView" />
-
- <!-- Positive Button -->
- <Button
- android:id="@+id/button_confirm"
- style="@*android:style/Widget.DeviceDefault.Button.Colored"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginBottom="8dp"
- android:layout_marginRight="8dp"
- android:ellipsize="end"
- android:maxLines="2"
- android:text="@string/biometric_dialog_confirm"
- android:visibility="invisible"
- app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline"
- app:layout_constraintEnd_toEndOf="@+id/scrollView"
- tools:visibility="invisible" />
-
- <!-- Try Again Button -->
- <Button
- android:id="@+id/button_try_again"
- style="@*android:style/Widget.DeviceDefault.Button.Colored"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginBottom="8dp"
- android:layout_marginRight="8dp"
- android:ellipsize="end"
- android:maxLines="2"
- android:text="@string/biometric_dialog_try_again"
- android:visibility="invisible"
- app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline"
- app:layout_constraintEnd_toEndOf="@+id/scrollView" />
+ <include
+ android:id="@+id/button_bar"
+ layout="@layout/biometric_prompt_button_bar"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ app:layout_constraintBottom_toTopOf="@id/bottomGuideline"
+ app:layout_constraintEnd_toEndOf="@id/scrollView"
+ app:layout_constraintStart_toStartOf="@id/scrollView"
+ app:layout_constraintTop_toBottomOf="@id/scrollView" />
<!-- Guidelines for setting panel border -->
<androidx.constraintlayout.widget.Barrier
@@ -282,7 +220,7 @@
android:layout_height="wrap_content"
app:barrierAllowsGoneWidgets="false"
app:barrierDirection="top"
- app:constraint_referenced_ids="button_negative, button_cancel, button_use_credential, button_confirm, button_try_again" />
+ app:constraint_referenced_ids="button_bar" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/leftGuideline"
diff --git a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
index 5b30dfb..329fc46 100644
--- a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
@@ -23,92 +23,29 @@
android:clickable="true"
android:clipToOutline="true"
android:importantForAccessibility="no"
- android:paddingHorizontal="16dp"
- android:paddingVertical="16dp"
android:visibility="visible"
- app:layout_constraintBottom_toTopOf="@+id/bottomGuideline"
+ app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/rightGuideline"
app:layout_constraintStart_toStartOf="@+id/leftGuideline"
app:layout_constraintTop_toTopOf="@+id/topBarrier" />
- <Button
- android:id="@+id/button_negative"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginBottom="8dp"
- android:layout_marginLeft="8dp"
- android:ellipsize="end"
- android:maxLines="2"
- android:visibility="invisible"
- app:layout_constraintBottom_toBottomOf="@+id/panel"
- app:layout_constraintStart_toStartOf="@+id/panel" />
-
- <Button
- android:id="@+id/button_cancel"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginBottom="8dp"
- android:layout_marginLeft="8dp"
- android:text="@string/cancel"
- android:visibility="invisible"
- app:layout_constraintBottom_toBottomOf="@+id/panel"
- app:layout_constraintStart_toStartOf="@+id/panel" />
-
- <Button
- android:id="@+id/button_use_credential"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginBottom="8dp"
- android:layout_marginLeft="8dp"
- android:visibility="invisible"
- app:layout_constraintBottom_toBottomOf="@+id/panel"
- app:layout_constraintStart_toStartOf="@+id/panel" />
-
- <Button
- android:id="@+id/button_confirm"
- style="@*android:style/Widget.DeviceDefault.Button.Colored"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginBottom="8dp"
- android:layout_marginRight="8dp"
- android:ellipsize="end"
- android:maxLines="2"
- android:text="@string/biometric_dialog_confirm"
- android:visibility="invisible"
- app:layout_constraintBottom_toBottomOf="@+id/panel"
- app:layout_constraintEnd_toEndOf="@+id/panel"
- tools:visibility="invisible" />
-
- <Button
- android:id="@+id/button_try_again"
- style="@*android:style/Widget.DeviceDefault.Button.Colored"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginBottom="8dp"
- android:layout_marginRight="8dp"
- android:ellipsize="end"
- android:maxLines="2"
- android:text="@string/biometric_dialog_try_again"
- android:visibility="invisible"
- app:layout_constraintBottom_toBottomOf="@+id/panel"
- app:layout_constraintEnd_toEndOf="@+id/panel" />
-
- <!-- Negative Button, reserved for app -->
+ <include
+ layout="@layout/biometric_prompt_button_bar"
+ android:id="@+id/button_bar"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ app:layout_constraintBottom_toTopOf="@id/bottomGuideline"
+ app:layout_constraintEnd_toEndOf="@id/panel"
+ app:layout_constraintStart_toStartOf="@id/panel"/>
<ScrollView
android:id="@+id/scrollView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:fillViewport="true"
- android:padding="16dp"
+ android:paddingBottom="36dp"
+ android:paddingHorizontal="24dp"
+ android:paddingTop="24dp"
app:layout_constrainedHeight="true"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
@@ -134,18 +71,19 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
- <LinearLayout
- android:id="@+id/customized_view_container"
- android:layout_width="wrap_content"
+ <TextView
+ android:id="@+id/logo_description"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:gravity="center_vertical"
- android:orientation="vertical"
- android:paddingHorizontal="@dimen/biometric_prompt_content_container_padding_horizontal"
- android:visibility="gone"
- app:layout_constraintBottom_toBottomOf="parent"
+ android:ellipsize="marquee"
+ android:gravity="@integer/biometric_dialog_text_gravity"
+ android:marqueeRepeatLimit="1"
+ android:singleLine="true"
+ android:paddingTop="16dp"
+ app:layout_constraintBottom_toTopOf="@+id/title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/subtitle" />
+ app:layout_constraintTop_toBottomOf="@+id/logo" />
<Space
android:id="@+id/space_above_content"
@@ -159,6 +97,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="@integer/biometric_dialog_text_gravity"
+ android:paddingTop="16dp"
app:layout_constraintBottom_toTopOf="@+id/subtitle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -170,23 +109,24 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="@integer/biometric_dialog_text_gravity"
+ android:paddingTop="16dp"
app:layout_constraintBottom_toTopOf="@+id/contentBarrier"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
- <TextView
- android:id="@+id/logo_description"
- android:layout_width="match_parent"
+ <LinearLayout
+ android:id="@+id/customized_view_container"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:ellipsize="marquee"
- android:gravity="@integer/biometric_dialog_text_gravity"
- android:marqueeRepeatLimit="1"
- android:singleLine="true"
- app:layout_constraintBottom_toTopOf="@+id/title"
+ android:gravity="center_vertical"
+ android:orientation="vertical"
+ android:paddingHorizontal="@dimen/biometric_prompt_content_container_padding_horizontal"
+ android:visibility="gone"
+ app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/logo" />
+ app:layout_constraintTop_toBottomOf="@+id/subtitle" />
<TextView
android:id="@+id/description"
@@ -215,7 +155,7 @@
android:id="@+id/indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
+ android:layout_marginTop="24dp"
android:accessibilityLiveRegion="polite"
android:fadingEdge="horizontal"
android:gravity="center_horizontal"
@@ -247,7 +187,7 @@
android:layout_height="wrap_content"
app:barrierAllowsGoneWidgets="false"
app:barrierDirection="top"
- app:constraint_referenced_ids="button_negative, button_cancel, button_use_credential, button_confirm, button_try_again" />
+ app:constraint_referenced_ids="button_bar" />
<!-- Guidelines for setting panel border -->
<androidx.constraintlayout.widget.Guideline
diff --git a/packages/SystemUI/res/layout/app_clips_screenshot.xml b/packages/SystemUI/res/layout/app_clips_screenshot.xml
index cb638ee..bcc7bca 100644
--- a/packages/SystemUI/res/layout/app_clips_screenshot.xml
+++ b/packages/SystemUI/res/layout/app_clips_screenshot.xml
@@ -69,7 +69,7 @@
tools:minHeight="100dp"
tools:minWidth="100dp" />
- <com.android.systemui.screenshot.CropView
+ <com.android.systemui.screenshot.scroll.CropView
android:id="@+id/crop_view"
android:layout_width="0px"
android:layout_height="0px"
diff --git a/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
new file mode 100644
index 0000000..810c7433
--- /dev/null
+++ b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
@@ -0,0 +1,91 @@
+<?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.
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <!-- Negative Button, reserved for app -->
+ <Button
+ android:id="@+id/button_negative"
+ style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginLeft="24dp"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <!-- Cancel Button, replaces negative button when biometric is accepted -->
+ <Button
+ android:id="@+id/button_cancel"
+ style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginLeft="24dp"
+ android:text="@string/cancel"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <!-- "Use Credential" Button, replaces if device credential is allowed -->
+ <Button
+ android:id="@+id/button_use_credential"
+ style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginLeft="24dp"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <!-- Positive Button -->
+ <Button
+ android:id="@+id/button_confirm"
+ style="@*android:style/Widget.DeviceDefault.Button.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginRight="24dp"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:text="@string/biometric_dialog_confirm"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+ <!-- Try Again Button -->
+ <Button
+ android:id="@+id/button_try_again"
+ style="@*android:style/Widget.DeviceDefault.Button.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginRight="24dp"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:text="@string/biometric_dialog_try_again"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
index 74292b4..6391813 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
@@ -23,13 +23,189 @@
android:clickable="true"
android:clipToOutline="true"
android:importantForAccessibility="no"
- android:paddingHorizontal="16dp"
- android:paddingVertical="16dp"
android:visibility="visible"
- app:layout_constraintBottom_toTopOf="@+id/bottomGuideline"
- app:layout_constraintEnd_toStartOf="@+id/rightGuideline"
- app:layout_constraintStart_toStartOf="@+id/leftGuideline"
- app:layout_constraintTop_toTopOf="@+id/topBarrier" />
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="@id/rightGuideline"
+ app:layout_constraintStart_toStartOf="@id/leftGuideline"
+ app:layout_constraintTop_toTopOf="@id/topBarrier" />
+
+ <include
+ android:id="@+id/button_bar"
+ layout="@layout/biometric_prompt_button_bar"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ app:layout_constraintBottom_toTopOf="@id/bottomGuideline"
+ app:layout_constraintEnd_toEndOf="@id/panel"
+ app:layout_constraintStart_toStartOf="@id/panel" />
+
+ <ScrollView
+ android:id="@+id/scrollView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fillViewport="true"
+ android:paddingBottom="36dp"
+ android:paddingHorizontal="24dp"
+ android:paddingTop="24dp"
+ app:layout_constrainedHeight="true"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
+ app:layout_constraintEnd_toEndOf="@id/rightGuideline"
+ app:layout_constraintStart_toStartOf="@id/leftGuideline"
+ app:layout_constraintTop_toTopOf="@+id/topGuideline"
+ app:layout_constraintVertical_bias="1.0">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/innerConstraint"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:id="@+id/logo"
+ android:layout_width="@dimen/biometric_auth_icon_size"
+ android:layout_height="@dimen/biometric_auth_icon_size"
+ android:layout_gravity="center"
+ android:scaleType="fitXY"
+ app:layout_constraintBottom_toTopOf="@+id/logo_description"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:visibility="visible" />
+
+ <TextView
+ android:id="@+id/logo_description"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="@integer/biometric_dialog_text_gravity"
+ android:singleLine="true"
+ app:layout_constraintBottom_toTopOf="@+id/title"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/logo" />
+
+ <Space
+ android:id="@+id/space_above_content"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/biometric_prompt_space_above_content"
+ android:visibility="gone" />
+
+ <TextView
+ android:id="@+id/title"
+ style="@style/TextAppearance.AuthCredential.Title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="@integer/biometric_dialog_text_gravity"
+ android:paddingTop="16dp"
+ app:layout_constraintBottom_toTopOf="@+id/subtitle"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/logo_description" />
+
+ <TextView
+ android:id="@+id/subtitle"
+ style="@style/TextAppearance.AuthCredential.Subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="@integer/biometric_dialog_text_gravity"
+ android:paddingTop="16dp"
+ app:layout_constraintBottom_toTopOf="@+id/contentBarrier"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/title" />
+
+ <LinearLayout
+ android:id="@+id/customized_view_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:orientation="vertical"
+ android:visibility="gone"
+ android:paddingTop="8dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/subtitle" />
+
+ <TextView
+ android:id="@+id/description"
+ style="@style/TextAppearance.AuthCredential.Description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="@integer/biometric_dialog_text_gravity"
+ android:paddingTop="16dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/subtitle" />
+
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/contentBarrier"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:barrierAllowsGoneWidgets="false"
+ app:barrierDirection="top"
+ app:constraint_referenced_ids="description, customized_view_container" />
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+ </ScrollView>
+
+ <TextView
+ android:id="@+id/indicator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="24dp"
+ android:accessibilityLiveRegion="polite"
+ android:fadingEdge="horizontal"
+ android:gravity="center_horizontal"
+ android:scrollHorizontally="true"
+ app:layout_constraintBottom_toTopOf="@+id/buttonBarrier"
+ app:layout_constraintEnd_toEndOf="@+id/panel"
+ app:layout_constraintStart_toStartOf="@+id/panel"
+ app:layout_constraintTop_toBottomOf="@+id/biometric_icon"
+ app:layout_constraintVertical_bias="0.0" />
+
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/topBarrier"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:barrierAllowsGoneWidgets="false"
+ app:barrierDirection="top"
+ app:constraint_referenced_ids="scrollView" />
+
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/buttonBarrier"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:barrierAllowsGoneWidgets="false"
+ app:barrierDirection="top"
+ app:constraint_referenced_ids="button_bar" />
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/leftGuideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintGuide_begin="0dp" />
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/rightGuideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintGuide_end="0dp" />
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/bottomGuideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/topGuideline"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:orientation="horizontal"
+ app:layout_constraintGuide_percent="0.25" />
<com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
android:id="@+id/biometric_icon"
@@ -54,249 +230,4 @@
app:layout_constraintStart_toStartOf="@+id/biometric_icon"
app:layout_constraintTop_toTopOf="@+id/biometric_icon" />
- <ScrollView
- android:id="@+id/scrollView"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:fillViewport="true"
- android:padding="16dp"
- app:layout_constrainedHeight="true"
- app:layout_constrainedWidth="true"
- app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
- app:layout_constraintEnd_toEndOf="@id/rightGuideline"
- app:layout_constraintStart_toStartOf="@id/leftGuideline"
- app:layout_constraintTop_toTopOf="@+id/topGuideline"
- app:layout_constraintVertical_bias="1.0">
-
- <androidx.constraintlayout.widget.ConstraintLayout
- android:id="@+id/innerConstraint"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <ImageView
- android:id="@+id/logo"
- android:layout_width="@dimen/biometric_auth_icon_size"
- android:layout_height="@dimen/biometric_auth_icon_size"
- android:layout_gravity="center"
- android:scaleType="fitXY"
- android:visibility="visible"
- app:layout_constraintBottom_toTopOf="@+id/logo_description"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
-
- <LinearLayout
- android:id="@+id/customized_view_container"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center_vertical"
- android:orientation="vertical"
- android:paddingHorizontal="@dimen/biometric_prompt_content_container_padding_horizontal"
- android:visibility="gone"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/subtitle" />
-
- <Space
- android:id="@+id/space_above_content"
- android:layout_width="match_parent"
- android:layout_height="@dimen/biometric_prompt_space_above_content"
- android:visibility="gone" />
-
- <TextView
- android:id="@+id/title"
- style="@style/TextAppearance.AuthCredential.Title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="@integer/biometric_dialog_text_gravity"
- app:layout_constraintBottom_toTopOf="@+id/subtitle"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/logo_description" />
-
- <TextView
- android:id="@+id/subtitle"
- style="@style/TextAppearance.AuthCredential.Subtitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="@integer/biometric_dialog_text_gravity"
- app:layout_constraintBottom_toTopOf="@+id/contentBarrier"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/title" />
-
- <TextView
- android:id="@+id/logo_description"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:ellipsize="marquee"
- android:gravity="@integer/biometric_dialog_text_gravity"
- android:marqueeRepeatLimit="1"
- android:singleLine="true"
- app:layout_constraintBottom_toTopOf="@+id/title"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/logo" />
-
- <TextView
- android:id="@+id/description"
- style="@style/TextAppearance.AuthCredential.Description"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="@integer/biometric_dialog_text_gravity"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/subtitle" />
-
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/contentBarrier"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:barrierAllowsGoneWidgets="false"
- app:barrierDirection="top"
- app:constraint_referenced_ids="description, customized_view_container" />
-
- </androidx.constraintlayout.widget.ConstraintLayout>
- </ScrollView>
-
- <TextView
- android:id="@+id/indicator"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
- android:accessibilityLiveRegion="polite"
- android:fadingEdge="horizontal"
- android:gravity="center_horizontal"
- android:marqueeRepeatLimit="marquee_forever"
- android:scrollHorizontally="true"
- android:textColor="@color/biometric_dialog_gray"
- android:textSize="12sp"
- app:layout_constraintBottom_toTopOf="@+id/buttonBarrier"
- app:layout_constraintEnd_toEndOf="@+id/panel"
- app:layout_constraintStart_toStartOf="@+id/panel"
- app:layout_constraintTop_toBottomOf="@+id/biometric_icon"
- app:layout_constraintVertical_bias="0.0" />
-
- <!-- Negative Button, reserved for app -->
- <Button
- android:id="@+id/button_negative"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginBottom="8dp"
- android:layout_marginLeft="8dp"
- android:ellipsize="end"
- android:maxLines="2"
- android:visibility="invisible"
- app:layout_constraintBottom_toBottomOf="@+id/panel"
- app:layout_constraintStart_toStartOf="@+id/panel" />
-
- <!-- Cancel Button, replaces negative button when biometric is accepted -->
- <Button
- android:id="@+id/button_cancel"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginBottom="8dp"
- android:layout_marginLeft="8dp"
- android:text="@string/cancel"
- android:visibility="invisible"
- app:layout_constraintBottom_toBottomOf="@+id/panel"
- app:layout_constraintStart_toStartOf="@+id/panel" />
-
- <!-- "Use Credential" Button, replaces if device credential is allowed -->
- <Button
- android:id="@+id/button_use_credential"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginBottom="8dp"
- android:layout_marginLeft="8dp"
- android:visibility="invisible"
- app:layout_constraintBottom_toBottomOf="@+id/panel"
- app:layout_constraintStart_toStartOf="@+id/panel" />
-
- <!-- Positive Button -->
- <Button
- android:id="@+id/button_confirm"
- style="@*android:style/Widget.DeviceDefault.Button.Colored"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginBottom="8dp"
- android:layout_marginRight="8dp"
- android:ellipsize="end"
- android:maxLines="2"
- android:text="@string/biometric_dialog_confirm"
- android:visibility="invisible"
- app:layout_constraintBottom_toBottomOf="@+id/panel"
- app:layout_constraintEnd_toEndOf="@+id/panel"
- tools:visibility="invisible" />
-
- <!-- Try Again Button -->
- <Button
- android:id="@+id/button_try_again"
- style="@*android:style/Widget.DeviceDefault.Button.Colored"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginBottom="8dp"
- android:layout_marginRight="8dp"
- android:ellipsize="end"
- android:maxLines="2"
- android:text="@string/biometric_dialog_try_again"
- android:visibility="invisible"
- app:layout_constraintBottom_toBottomOf="@+id/panel"
- app:layout_constraintEnd_toEndOf="@+id/panel" />
-
- <!-- Guidelines for setting panel border -->
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/topBarrier"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:barrierAllowsGoneWidgets="false"
- app:barrierDirection="top"
- app:constraint_referenced_ids="scrollView" />
-
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/buttonBarrier"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:barrierAllowsGoneWidgets="false"
- app:barrierDirection="top"
- app:constraint_referenced_ids="button_negative, button_cancel, button_use_credential, button_confirm, button_try_again" />
-
- <androidx.constraintlayout.widget.Guideline
- android:id="@+id/leftGuideline"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" />
-
- <androidx.constraintlayout.widget.Guideline
- android:id="@+id/rightGuideline"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
-
- <androidx.constraintlayout.widget.Guideline
- android:id="@+id/bottomGuideline"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
-
- <androidx.constraintlayout.widget.Guideline
- android:id="@+id/topGuideline"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:orientation="horizontal"
- app:layout_constraintGuide_percent="0.25171" />
-
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml
index 8a19c2e..4d207da 100644
--- a/packages/SystemUI/res/layout/long_screenshot.xml
+++ b/packages/SystemUI/res/layout/long_screenshot.xml
@@ -100,7 +100,7 @@
app:layout_constraintStart_toStartOf="parent"
android:transitionName="screenshot_preview_image"/>
- <com.android.systemui.screenshot.CropView
+ <com.android.systemui.screenshot.scroll.CropView
android:id="@+id/crop_view"
android:layout_width="0px"
android:layout_height="0px"
@@ -122,7 +122,7 @@
tools:minHeight="100dp"
tools:minWidth="100dp" />
- <com.android.systemui.screenshot.MagnifierView
+ <com.android.systemui.screenshot.scroll.MagnifierView
android:id="@+id/magnifier"
android:visibility="invisible"
android:layout_width="200dp"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b7eff38..cac3289 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -898,8 +898,9 @@
<dimen name="communal_tutorial_indicator_horizontal_offset">32dp</dimen>
<!-- Size of the maximum radius for the enforced rounded rectangles on communal hub.
- Keep it the same as in Launcher-->
- <dimen name="communal_enforced_rounded_corner_max_radius">16dp</dimen>
+ Larger than the 16dp Launcher uses, to ensure consistency on the hub, where it's much more
+ obvious when corner radii differ.-->
+ <dimen name="communal_enforced_rounded_corner_max_radius">28dp</dimen>
<!-- Width and height used to filter widgets displayed in the communal widget picker -->
<dimen name="communal_widget_picker_desired_width">424dp</dimen>
@@ -1087,7 +1088,7 @@
<dimen name="biometric_dialog_fingerprint_icon_height">80dp</dimen>
<dimen name="biometric_dialog_button_negative_max_width">160dp</dimen>
<dimen name="biometric_dialog_button_positive_max_width">136dp</dimen>
- <dimen name="biometric_dialog_corner_size">4dp</dimen>
+ <dimen name="biometric_dialog_corner_size">28dp</dimen>
<!-- Y translation when showing/dismissing the dialog-->
<dimen name="biometric_dialog_animation_translation_offset">350dp</dimen>
<dimen name="biometric_dialog_border_padding">4dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b8f20f6..774bbe5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1556,11 +1556,11 @@
<string name="volume_panel_noise_control_title">Noise Control</string>
<!-- Label for button to enabled/disable spatial audio [CHAR_LIMIT=30] -->
<string name="volume_panel_spatial_audio_title">Spatial Audio</string>
- <!-- Label for button to disable spatial audio [CHAR_LIMIT=20] -->
+ <!-- Label for a spatial audio button for the case when it is disabled [CHAR_LIMIT=20] -->
<string name="volume_panel_spatial_audio_off">Off</string>
- <!-- Label for button to enabled spatial audio [CHAR_LIMIT=20] -->
+ <!-- Label for a spatial audio button for the case when it is enabled without head tracking [CHAR_LIMIT=20] -->
<string name="volume_panel_spatial_audio_fixed">Fixed</string>
- <!-- Label for button to enabled head tracking [CHAR_LIMIT=20] -->
+ <!-- Label for a spatial audio button for the case when it is enabled with head tracking [CHAR_LIMIT=20] -->
<string name="volume_panel_spatial_audio_tracking">Head Tracking</string>
<string name="volume_ringer_change">Tap to change ringer mode</string>
@@ -1576,6 +1576,18 @@
<string name="volume_dialog_ringer_guidance_ring">Calls and notifications will ring (<xliff:g id="volume level" example="56">%1$s</xliff:g>)</string>
+ <!-- An audible a11y label for a button, that opens settings when clicked [CHAR_LIMIT=NONE] -->
+ <string name="volume_panel_enter_media_output_settings">Enter output settings</string>
+ <!-- An audible a11y state description for a button, that expands volume sliders menu [CHAR_LIMIT=NONE] -->
+ <string name="volume_panel_expanded_sliders">Volume sliders expanded</string>
+ <!-- An audible a11y state description for a button, that collapses volume sliders menu [CHAR LIMIT=NONE] -->
+ <string name="volume_panel_collapsed_sliders">Volume sliders collapsed</string>
+
+ <!-- Hint for accessibility. A stream name is a parameter. For example: double tap to mute media [CHAR_LIMIT=NONE] -->
+ <string name="volume_panel_hint_mute">mute %s</string>
+ <!-- Hint for accessibility. A stream name is a parameter. For example: double tap to unmute media [CHAR_LIMIT=NONE] -->
+ <string name="volume_panel_hint_unmute">unmute %s</string>
+
<!-- Title with application label for media output settings. [CHAR LIMIT=20] -->
<string name="media_output_label_title">Playing <xliff:g id="label" example="Music Player">%s</xliff:g> on</string>
@@ -3346,7 +3358,7 @@
<string name="microphone_blocked_dream_overlay_content_description">Microphone blocked</string>
<!-- Content description for priority mode icon on dream [CHAR LIMIT=NONE] -->
- <string name="priority_mode_dream_overlay_content_description">Priority mode on</string>
+ <string name="priority_mode_dream_overlay_content_description">Do not disturb</string>
<!-- Content description for when assistant attention is active [CHAR LIMIT=NONE] -->
<string name="assistant_attention_content_description">User presence is detected</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 0bb5c17..fb331b6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -15,8 +15,10 @@
*/
package com.android.keyguard;
+import android.annotation.NonNull;
import android.app.Presentation;
import android.content.Context;
+import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.DisplayManager;
import android.media.MediaRouter;
@@ -315,11 +317,11 @@
}
@Override
- public void onStateChanged(int state) {
+ public void onDeviceStateChanged(@NonNull DeviceState state) {
// When concurrent state ends, the display also turns off. This is enforced in various
// ExtensionRearDisplayPresentationTest CTS tests. So, we don't need to invoke
// hide() since that will happen through the onDisplayRemoved callback.
- mIsInConcurrentDisplayState = state == mConcurrentState;
+ mIsInConcurrentDisplayState = state.getIdentifier() == mConcurrentState;
}
boolean isConcurrentDisplayActive(Display display) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index c0ae4a1..7f9ae5e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -250,6 +250,10 @@
@Override
protected void onViewAttached() {
mStatusArea = mView.findViewById(R.id.keyguard_status_area);
+ if (migrateClocksToBlueprint()) {
+ return;
+ }
+
mStatusArea.addOnLayoutChangeListener(mStatusAreaLayoutChangeListener);
mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
mConfigurationController.addCallback(mConfigurationListener);
@@ -257,6 +261,10 @@
@Override
protected void onViewDetached() {
+ if (migrateClocksToBlueprint()) {
+ return;
+ }
+
mStatusArea.removeOnLayoutChangeListener(mStatusAreaLayoutChangeListener);
mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
mConfigurationController.removeCallback(mConfigurationListener);
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 8e98150..039a2e5 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -23,6 +23,7 @@
import static com.android.keyguard.LockIconView.ICON_LOCK;
import static com.android.keyguard.LockIconView.ICON_UNLOCK;
import static com.android.systemui.Flags.keyguardBottomAreaRefactor;
+import static com.android.systemui.Flags.migrateClocksToBlueprint;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
@@ -452,7 +453,7 @@
private void updateLockIconLocation() {
final float scaleFactor = mAuthController.getScaleFactor();
final int scaledPadding = (int) (mDefaultPaddingPx * scaleFactor);
- if (keyguardBottomAreaRefactor()) {
+ if (keyguardBottomAreaRefactor() || migrateClocksToBlueprint()) {
mView.getLockIcon().setPadding(scaledPadding, scaledPadding, scaledPadding,
scaledPadding);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index da49201..c6716e4 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -54,7 +54,7 @@
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
@@ -190,7 +190,7 @@
private final NotificationShadeWindowController mNotificationShadeController;
private final KeyguardStateController mKeyguardStateController;
private final ShadeController mShadeController;
- private final Lazy<ShadeViewController> mShadeViewController;
+ private final Lazy<PanelExpansionInteractor> mPanelExpansionInteractor;
private final StatusBarWindowCallback mNotificationShadeCallback;
private boolean mDismissNotificationShadeActionRegistered;
@@ -200,14 +200,14 @@
NotificationShadeWindowController notificationShadeController,
KeyguardStateController keyguardStateController,
ShadeController shadeController,
- Lazy<ShadeViewController> shadeViewController,
+ Lazy<PanelExpansionInteractor> panelExpansionInteractor,
Optional<Recents> recentsOptional,
DisplayTracker displayTracker) {
mContext = context;
mUserTracker = userTracker;
mKeyguardStateController = keyguardStateController;
mShadeController = shadeController;
- mShadeViewController = shadeViewController;
+ mPanelExpansionInteractor = panelExpansionInteractor;
mRecentsOptional = recentsOptional;
mDisplayTracker = displayTracker;
mReceiver = new SystemActionsBroadcastReceiver();
@@ -330,7 +330,7 @@
private void registerOrUnregisterDismissNotificationShadeAction() {
Assert.isMainThread();
- if (mShadeViewController.get().isPanelExpanded()
+ if (mPanelExpansionInteractor.get().isPanelExpanded()
&& !mKeyguardStateController.isShowing()) {
if (!mDismissNotificationShadeActionRegistered) {
mA11yManager.registerSystemAction(
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
index 9dd1454..0f4d63c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
@@ -302,6 +302,14 @@
if (mEndAnimationCanceled || mController == null) {
return;
}
+
+ // If the animation is playing backwards, mStartSpec will be the final spec we would
+ // like to reach.
+ AnimationSpec spec = isReverse ? mStartSpec : mEndSpec;
+ mController.enableWindowMagnificationInternal(
+ spec.mScale, spec.mCenterX, spec.mCenterY,
+ mMagnificationFrameOffsetRatioX, mMagnificationFrameOffsetRatioY);
+
if (mState == STATE_DISABLING) {
mController.deleteWindowMagnification();
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
index 0ef3d20..a90d4b2 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
@@ -118,6 +118,12 @@
iconResId = R.drawable.pip_ic_close_white
)
)
+
+ // Ensure this is unfocusable & uninteractable
+ isClickable = false
+ isFocusable = false
+ importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
+
// END DragToInteractView modification
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index d3e85e0..1f04599 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -113,13 +113,8 @@
/* Moves position without updating underlying percentage position. Can be animated. */
void moveToPosition(PointF position, boolean animateMovement) {
- if (Flags.floatingMenuImeDisplacementAnimation()) {
- moveToPositionX(position.x, animateMovement);
- moveToPositionY(position.y, animateMovement);
- } else {
- moveToPositionX(position.x, /* animateMovement = */ false);
- moveToPositionY(position.y, /* animateMovement = */ false);
- }
+ moveToPositionX(position.x, animateMovement);
+ moveToPositionY(position.y, animateMovement);
}
void moveToPositionX(float positionX) {
@@ -127,7 +122,7 @@
}
void moveToPositionX(float positionX, boolean animateMovement) {
- if (animateMovement && Flags.floatingMenuImeDisplacementAnimation()) {
+ if (animateMovement) {
springMenuWith(DynamicAnimation.TRANSLATION_X,
createSpringForce(),
/* velocity = */ 0,
@@ -142,7 +137,7 @@
}
void moveToPositionY(float positionY, boolean animateMovement) {
- if (animateMovement && Flags.floatingMenuImeDisplacementAnimation()) {
+ if (animateMovement) {
springMenuWith(DynamicAnimation.TRANSLATION_Y,
createSpringForce(),
/* velocity = */ 0,
@@ -455,7 +450,7 @@
? MIN_PERCENT
: Math.min(MAX_PERCENT, position.y / draggableBounds.height());
- if (Flags.floatingMenuImeDisplacementAnimation() && !writeToPosition) {
+ if (!writeToPosition) {
mMenuView.onEdgeChangedIfNeeded();
} else {
mMenuView.persistPositionAndUpdateEdge(new Position(percentageX, percentageY));
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
index e57323b..35fe6b1 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
@@ -74,6 +74,12 @@
addView(mTextView, Index.TEXT_VIEW,
new LayoutParams(/* width= */ 0, WRAP_CONTENT, /* weight= */ 1));
addView(mUndoButton, Index.UNDO_BUTTON, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+
+ // The message box is not focusable, but will announce its contents when it appears.
+ // The textView and button are still interactable.
+ setClickable(false);
+ setFocusable(false);
+ setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index 577bbc0..0c9712d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -21,17 +21,10 @@
import android.annotation.SuppressLint;
import android.content.ComponentCallbacks;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.provider.SettingsStringUtil;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
@@ -101,6 +94,10 @@
loadLayoutResources();
addView(mTargetFeaturesView);
+
+ setClickable(false);
+ setFocusable(false);
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
}
@Override
@@ -224,8 +221,7 @@
}
// We can skip animating if FAB is not visible
- if (Flags.floatingMenuImeDisplacementAnimation()
- && animateMovement && getVisibility() == VISIBLE) {
+ if (animateMovement && getVisibility() == VISIBLE) {
mMenuAnimationController.moveToPosition(position, /* animateMovement = */ true);
// onArrivalAtPosition() is called at the end of the animation.
} else {
@@ -331,7 +327,7 @@
mMoveToTuckedListener.onMoveToTuckedChanged(isMoveToTucked);
}
- if (Flags.floatingMenuOverlapsNavBarsFlag() && !Flags.floatingMenuAnimatedTuck()) {
+ if (!Flags.floatingMenuAnimatedTuck()) {
if (isMoveToTucked) {
final float halfWidth = getMenuWidth() / 2.0f;
final boolean isOnLeftSide = mMenuAnimationController.isOnLeftSide();
@@ -431,22 +427,6 @@
onPositionChanged();
}
- void gotoEditScreen() {
- if (!Flags.floatingMenuDragToEdit()) {
- return;
- }
- mMenuAnimationController.flingMenuThenSpringToEdge(
- getMenuPosition().x, 100f, 0f);
-
- Intent intent = getIntentForEditScreen();
- PackageManager packageManager = getContext().getPackageManager();
- List<ResolveInfo> activities = packageManager.queryIntentActivities(intent,
- PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY));
- if (!activities.isEmpty()) {
- mContext.startActivity(intent);
- }
- }
-
void incrementTexMetricForAllTargets(String metric) {
if (!Flags.floatingMenuDragToEdit()) {
return;
@@ -461,23 +441,6 @@
Counter.logIncrementWithUid(metric, uid);
}
- Intent getIntentForEditScreen() {
- List<String> targets = new SettingsStringUtil.ColonDelimitedSet.OfStrings(
- mSecureSettings.getStringForUser(
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
- UserHandle.USER_CURRENT)).stream().toList();
-
- Intent intent = new Intent(
- Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS);
- Bundle args = new Bundle();
- Bundle fragmentArgs = new Bundle();
- fragmentArgs.putStringArray("targets", targets.toArray(new String[0]));
- args.putBundle(":settings:show_fragment_args", fragmentArgs);
- intent.replaceExtras(args);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- return intent;
- }
-
private InstantInsetLayerDrawable getContainerViewInsetLayer() {
return (InstantInsetLayerDrawable) getBackground();
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
index 4865fce..760e1c3 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
@@ -34,7 +34,6 @@
import androidx.annotation.DimenRes;
-import com.android.systemui.Flags;
import com.android.systemui.res.R;
import java.lang.annotation.Retention;
@@ -155,11 +154,6 @@
final int margin = getMenuMargin();
final Rect draggableBounds = new Rect(getWindowAvailableBounds());
- if (!Flags.floatingMenuOverlapsNavBarsFlag()) {
- // Initializes start position for mapping the translation of the menu view.
- draggableBounds.offsetTo(/* newLeft= */ 0, /* newTop= */ 0);
- }
-
draggableBounds.top += margin;
draggableBounds.right -= getMenuWidth();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index cd3b8a6..85bf784 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -36,19 +36,24 @@
import android.annotation.StringDef;
import android.annotation.SuppressLint;
import android.app.NotificationManager;
+import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.provider.Settings;
+import android.provider.SettingsStringUtil;
import android.util.ArraySet;
import android.view.MotionEvent;
import android.view.View;
@@ -120,6 +125,7 @@
private final MenuAnimationController mMenuAnimationController;
private final AccessibilityManager mAccessibilityManager;
private final NotificationManager mNotificationManager;
+ private StatusBarManager mStatusBarManager;
private final MenuNotificationFactory mNotificationFactory;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final IAccessibilityFloatingMenu mFloatingMenu;
@@ -246,6 +252,7 @@
mDismissView.getCircle().setId(R.id.action_remove_menu);
mNotificationFactory = new MenuNotificationFactory(context);
mNotificationManager = context.getSystemService(NotificationManager.class);
+ mStatusBarManager = context.getSystemService(StatusBarManager.class);
if (Flags.floatingMenuDragToEdit()) {
mDragToInteractAnimationController = new DragToInteractAnimationController(
@@ -319,6 +326,9 @@
if (Flags.floatingMenuAnimatedTuck()) {
setClipChildren(true);
}
+ setClickable(false);
+ setFocusable(false);
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
}
@Override
@@ -443,21 +453,18 @@
}
public void onMoveToTuckedChanged(boolean moveToTuck) {
- if (Flags.floatingMenuOverlapsNavBarsFlag()) {
- if (moveToTuck) {
- final Rect bounds = mMenuViewAppearance.getWindowAvailableBounds();
- final int[] location = getLocationOnScreen();
- bounds.offset(
- location[0],
- location[1]
- );
+ if (moveToTuck) {
+ final Rect bounds = mMenuViewAppearance.getWindowAvailableBounds();
+ final int[] location = getLocationOnScreen();
+ bounds.offset(
+ location[0],
+ location[1]
+ );
- setClipBounds(bounds);
- }
- // Instead of clearing clip bounds when moveToTuck is false,
- // wait until the spring animation finishes.
+ setClipBounds(bounds);
}
- // Function is a no-operation if flag is disabled.
+ // Instead of clearing clip bounds when moveToTuck is false,
+ // wait until the spring animation finishes.
}
private void onSpringAnimationsEndAction() {
@@ -475,9 +482,7 @@
setClipBounds(null);
}
}
- if (Flags.floatingMenuImeDisplacementAnimation()) {
- mMenuView.onArrivalAtPosition(false);
- }
+ mMenuView.onArrivalAtPosition(false);
}
void dispatchAccessibilityAction(int id) {
@@ -490,7 +495,7 @@
mMenuView.incrementTexMetricForAllTargets(TEX_METRIC_DISMISS);
} else if (id == R.id.action_edit
&& Flags.floatingMenuDragToEdit()) {
- mMenuView.gotoEditScreen();
+ gotoEditScreen();
mMenuView.incrementTexMetricForAllTargets(TEX_METRIC_EDIT);
}
mDismissView.hide();
@@ -499,6 +504,40 @@
id, /* scaleUp= */ false);
}
+ void gotoEditScreen() {
+ if (!Flags.floatingMenuDragToEdit()) {
+ return;
+ }
+ mMenuAnimationController.flingMenuThenSpringToEdge(
+ mMenuView.getMenuPosition().x, 100f, 0f);
+
+ Intent intent = getIntentForEditScreen();
+ PackageManager packageManager = getContext().getPackageManager();
+ List<ResolveInfo> activities = packageManager.queryIntentActivities(intent,
+ PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY));
+ if (!activities.isEmpty()) {
+ mContext.startActivity(intent);
+ mStatusBarManager.collapsePanels();
+ }
+ }
+
+ Intent getIntentForEditScreen() {
+ List<String> targets = new SettingsStringUtil.ColonDelimitedSet.OfStrings(
+ mSecureSettings.getStringForUser(
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+ UserHandle.USER_CURRENT)).stream().toList();
+
+ Intent intent = new Intent(
+ Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS);
+ Bundle args = new Bundle();
+ Bundle fragmentArgs = new Bundle();
+ fragmentArgs.putStringArray("targets", targets.toArray(new String[0]));
+ args.putBundle(":settings:show_fragment_args", fragmentArgs);
+ intent.replaceExtras(args);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ return intent;
+ }
+
private CharSequence getMigrationMessage() {
final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
index bc9d1ff..6b1240b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
@@ -20,11 +20,9 @@
import android.content.Context;
import android.graphics.PixelFormat;
-import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
-import com.android.systemui.Flags;
import com.android.systemui.util.settings.SecureSettings;
/**
@@ -88,14 +86,9 @@
params.privateFlags |= PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
params.windowAnimations = android.R.style.Animation_Translucent;
// Insets are configured to allow the menu to display over navigation and system bars.
- if (Flags.floatingMenuOverlapsNavBarsFlag()) {
- params.setFitInsetsTypes(0);
- params.layoutInDisplayCutoutMode =
- WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- } else {
- params.setFitInsetsTypes(
- WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
- }
+ params.setFitInsetsTypes(0);
+ params.layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
return params;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 8bd675c..99cdc01 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -340,13 +340,19 @@
mPanelInteractionDetector = panelInteractionDetector;
mApplicationCoroutineScope = applicationCoroutineScope;
+ mPromptViewModel = promptViewModel;
mTranslationY = getResources()
.getDimension(R.dimen.biometric_dialog_animation_translation_offset);
mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN;
mBiometricCallback = new BiometricCallback();
+ mPromptSelectorInteractorProvider = promptSelectorInteractorProvider;
+ mPromptSelectorInteractorProvider.get().setShouldShowBpWithoutIconForCredential(
+ config.mPromptInfo);
+
final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
- if (constraintBp()) {
+ if (constraintBp() && (Utils.isBiometricAllowed(config.mPromptInfo)
+ || mPromptViewModel.getShowBpWithoutIconForCredential().getValue())) {
mLayout = (ConstraintLayout) layoutInflater.inflate(
R.layout.biometric_prompt_constraint_layout, this, false /* attachToRoot */);
} else {
@@ -375,9 +381,7 @@
mBackgroundExecutor = bgExecutor;
mInteractionJankMonitor = jankMonitor;
mPromptCredentialInteractor = credentialInteractor;
- mPromptSelectorInteractorProvider = promptSelectorInteractorProvider;
mCredentialViewModelProvider = credentialViewModelProvider;
- mPromptViewModel = promptViewModel;
mFpProps = fpProps;
mFaceProps = faceProps;
@@ -408,10 +412,6 @@
@Nullable FaceSensorPropertiesInternal faceProps,
@NonNull VibratorHelper vibratorHelper
) {
- // Set this value before showing either of the prompt.
- mPromptSelectorInteractorProvider.get().setShouldShowBpWithoutIconForCredential(
- config.mPromptInfo);
-
if (Utils.isBiometricAllowed(config.mPromptInfo)
|| mPromptViewModel.getShowBpWithoutIconForCredential().getValue()) {
addBiometricView(config, layoutInflater, viewModel, fpProps, faceProps, vibratorHelper);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index 478ef8f..1dfd2e5f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -23,6 +23,7 @@
import android.graphics.Rect
import android.transition.AutoTransition
import android.transition.TransitionManager
+import android.util.TypedValue
import android.view.Surface
import android.view.View
import android.view.ViewGroup
@@ -54,13 +55,10 @@
import com.android.systemui.biometrics.ui.viewmodel.isRight
import com.android.systemui.biometrics.ui.viewmodel.isSmall
import com.android.systemui.biometrics.ui.viewmodel.isTop
-import com.android.systemui.keyguard.ui.view.layout.sections.setVisibility
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
-import kotlin.math.abs
import kotlin.math.min
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
/** Helper for [BiometricViewBinder] to handle resize transitions. */
@@ -106,6 +104,13 @@
val iconHolderView = view.requireViewById<View>(R.id.biometric_icon)
val panelView = view.requireViewById<View>(R.id.panel)
val cornerRadius = view.resources.getDimension(R.dimen.biometric_dialog_corner_size)
+ val cornerRadiusPx =
+ TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ cornerRadius,
+ view.resources.displayMetrics
+ )
+ .toInt()
// ConstraintSets for animating between prompt sizes
val mediumConstraintSet = ConstraintSet()
@@ -116,9 +121,10 @@
val largeConstraintSet = ConstraintSet()
largeConstraintSet.clone(mediumConstraintSet)
- largeConstraintSet.setGuidelineBegin(leftGuideline.id, 0)
- largeConstraintSet.setGuidelineEnd(rightGuideline.id, 0)
- largeConstraintSet.setGuidelineEnd(bottomGuideline.id, 0)
+ largeConstraintSet.setVisibility(iconHolderView.id, View.GONE)
+ largeConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
+ largeConstraintSet.setVisibility(R.id.indicator, View.GONE)
+ largeConstraintSet.setVisibility(R.id.scrollView, View.GONE)
// TODO: Investigate better way to handle 180 rotations
val flipConstraintSet = ConstraintSet()
@@ -141,7 +147,13 @@
panelView.outlineProvider =
object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
- outline.setRoundRect(0, 0, view.width, view.height, cornerRadius)
+ outline.setRoundRect(
+ 0,
+ 0,
+ view.width,
+ view.height + cornerRadiusPx,
+ cornerRadiusPx.toFloat()
+ )
}
}
@@ -164,35 +176,81 @@
when {
position.isTop -> {
+ // Round bottom corners
+ panelView.outlineProvider =
+ object : ViewOutlineProvider() {
+ override fun getOutline(view: View, outline: Outline) {
+ outline.setRoundRect(
+ 0,
+ -cornerRadiusPx,
+ view.width,
+ view.height,
+ cornerRadiusPx.toFloat()
+ )
+ }
+ }
left = windowBounds.centerX() - width / 2 + viewModel.promptMargin
right = windowBounds.centerX() - width / 2 + viewModel.promptMargin
bottom = iconHolderView.centerY() * 2 - iconHolderView.centerY() / 4
}
position.isBottom -> {
- left = windowBounds.centerX() - width / 2 + viewModel.promptMargin
- right = windowBounds.centerX() - width / 2 + viewModel.promptMargin
- bottom = viewModel.promptMargin
+ // Round top corners
+ panelView.outlineProvider =
+ object : ViewOutlineProvider() {
+ override fun getOutline(view: View, outline: Outline) {
+ outline.setRoundRect(
+ 0,
+ 0,
+ view.width,
+ view.height + cornerRadiusPx,
+ cornerRadiusPx.toFloat()
+ )
+ }
+ }
+
+ left = windowBounds.centerX() - width / 2
+ right = windowBounds.centerX() - width / 2
+ bottom = if (view.isLandscape()) bottomInset else 0
}
position.isLeft -> {
- left = viewModel.promptMargin
- mid =
- abs(
- windowBounds.width() - iconHolderView.centerX() * 2 +
- viewModel.promptMargin
- )
+ // Round right corners
+ panelView.outlineProvider =
+ object : ViewOutlineProvider() {
+ override fun getOutline(view: View, outline: Outline) {
+ outline.setRoundRect(
+ -cornerRadiusPx,
+ 0,
+ view.width,
+ view.height,
+ cornerRadiusPx.toFloat()
+ )
+ }
+ }
+
+ left = 0
+ mid = (windowBounds.width() * .85).toInt() / 2
right = windowBounds.width() - (windowBounds.width() * .85).toInt()
- bottom = bottomInset + viewModel.promptMargin
+ bottom = if (view.isLandscape()) bottomInset else 0
}
position.isRight -> {
+ // Round left corners
+ panelView.outlineProvider =
+ object : ViewOutlineProvider() {
+ override fun getOutline(view: View, outline: Outline) {
+ outline.setRoundRect(
+ 0,
+ 0,
+ view.width + cornerRadiusPx,
+ view.height,
+ cornerRadiusPx.toFloat()
+ )
+ }
+ }
+
left = windowBounds.width() - (windowBounds.width() * .85).toInt()
- right = viewModel.promptMargin
- bottom = bottomInset + viewModel.promptMargin
- mid =
- abs(
- iconHolderView.centerX() -
- (windowBounds.width() - iconHolderView.centerX()) -
- viewModel.promptMargin
- )
+ right = 0
+ bottom = if (view.isLandscape()) bottomInset else 0
+ mid = windowBounds.width() - (windowBounds.width() * .85).toInt() / 2
}
}
@@ -226,16 +284,8 @@
}
}
- fun setConstraintSetVisibility() {
- viewsToHideWhenSmall.forEach {
- mediumConstraintSet.setVisibility(it.id, it.showContentOrHide())
- largeConstraintSet.setVisibility(it.id, View.GONE)
- smallConstraintSet.setVisibility(it.id, View.GONE)
- }
-
- largeConstraintSet.setVisibility(iconHolderView.id, View.GONE)
- largeConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
- largeConstraintSet.setVisibility(R.id.indicator, View.GONE)
+ fun setVisibilities(size: PromptSize) {
+ viewsToHideWhenSmall.forEach { it.showContentOrHide(forceHide = size.isSmall) }
if (viewModel.showBpWithoutIconForCredential.value) {
smallConstraintSet.setVisibility(iconHolderView.id, View.GONE)
@@ -261,7 +311,7 @@
}
measureBounds(position)
- setConstraintSetVisibility()
+ setVisibilities(size)
when {
size.isSmall -> {
val ratio =
@@ -353,7 +403,7 @@
// prepare for animated size transitions
for (v in viewsToHideWhenSmall) {
- v.visibility = v.showContentOrHide(forceHide = size.isSmall)
+ v.showContentOrHide(forceHide = size.isSmall)
}
if (viewModel.showBpWithoutIconForCredential.value) {
@@ -490,14 +540,15 @@
}
}
-private fun View.showContentOrHide(forceHide: Boolean = false): Int {
+private fun View.showContentOrHide(forceHide: Boolean = false) {
val isTextViewWithBlankText = this is TextView && this.text.isBlank()
val isImageViewWithoutImage = this is ImageView && this.drawable == null
- return if (forceHide || isTextViewWithBlankText || isImageViewWithoutImage) {
- View.GONE
- } else {
- View.VISIBLE
- }
+ visibility =
+ if (forceHide || isTextViewWithBlankText || isImageViewWithoutImage) {
+ View.GONE
+ } else {
+ View.VISIBLE
+ }
}
private fun View.centerX(): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
index 3469cfa..d8265c7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -63,12 +63,6 @@
iconOverlayView.layoutParams.width = iconViewLayoutParamSizeOverride.first
iconOverlayView.layoutParams.height = iconViewLayoutParamSizeOverride.second
- } else {
- iconView.layoutParams.width = viewModel.fingerprintIconWidth.first()
- iconView.layoutParams.height = viewModel.fingerprintIconWidth.first()
-
- iconOverlayView.layoutParams.width = viewModel.fingerprintIconWidth.first()
- iconOverlayView.layoutParams.height = viewModel.fingerprintIconWidth.first()
}
var faceIcon: AnimatedVectorDrawable? = null
@@ -84,9 +78,12 @@
}
launch {
- var width = 0
- var height = 0
- viewModel.activeAuthType.collect { activeAuthType ->
+ combine(viewModel.activeAuthType, viewModel.iconSize, ::Pair).collect {
+ (activeAuthType, iconSize) ->
+ // Every time after bp shows, [isIconViewLoaded] is set to false in
+ // [BiometricViewSizeBinder]. Then when biometric prompt view is redrew
+ // (when size or activeAuthType changes), we need to update
+ // [isIconViewLoaded] here to keep it correct.
when (activeAuthType) {
AuthType.Fingerprint,
AuthType.Coex -> {
@@ -104,8 +101,6 @@
}
}
AuthType.Face -> {
- width = viewModel.faceIconWidth
- height = viewModel.faceIconHeight
/**
* Set to true by default since face icon is a drawable, which
* doesn't have a LottieOnCompositionLoadedListener equivalent.
@@ -117,11 +112,12 @@
}
}
- if (width != 0 && height != 0) {
- iconView.layoutParams.width = width
- iconView.layoutParams.height = height
- iconOverlayView.layoutParams.width = width
- iconOverlayView.layoutParams.height = height
+ if (iconViewLayoutParamSizeOverride == null) {
+ iconView.layoutParams.width = iconSize.first
+ iconView.layoutParams.height = iconSize.second
+
+ iconOverlayView.layoutParams.width = iconSize.first
+ iconOverlayView.layoutParams.height = iconSize.second
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
index 257eb4a..d0c140b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
@@ -21,6 +21,7 @@
import android.annotation.RawRes
import android.content.res.Configuration
import android.graphics.Rect
+import android.hardware.face.Face
import android.util.RotationUtils
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
@@ -65,9 +66,10 @@
*/
val activeAuthType: Flow<AuthType> =
combine(
+ promptViewModel.size,
promptViewModel.modalities.distinctUntilChanged(),
promptViewModel.faceMode.distinctUntilChanged()
- ) { modalities, faceMode ->
+ ) { _, modalities, faceMode ->
if (modalities.hasFaceAndFingerprint && !faceMode) {
AuthType.Coex
} else if (modalities.hasFaceOnly || faceMode) {
@@ -158,13 +160,18 @@
/** Tracks whether a face iconView last pulsed light to dark (vs. dark to light) */
val lastPulseLightToDark: Flow<Boolean> = _lastPulseLightToDark.asStateFlow()
- /** Layout params for fingerprint iconView */
- val fingerprintIconWidth: Flow<Int> = promptViewModel.fingerprintSensorDiameter
- val fingerprintIconHeight: Flow<Int> = promptViewModel.fingerprintSensorDiameter
-
- /** Layout params for face iconView */
- val faceIconWidth: Int = promptViewModel.faceIconWidth
- val faceIconHeight: Int = promptViewModel.faceIconHeight
+ val iconSize: Flow<Pair<Int, Int>> =
+ combine(
+ activeAuthType,
+ promptViewModel.fingerprintSensorWidth,
+ promptViewModel.fingerprintSensorHeight,
+ ) { activeAuthType, fingerprintSensorWidth, fingerprintSensorHeight ->
+ if (activeAuthType == AuthType.Face) {
+ Pair(promptViewModel.faceIconWidth, promptViewModel.faceIconHeight)
+ } else {
+ Pair(fingerprintSensorWidth, fingerprintSensorHeight)
+ }
+ }
/** Current BiometricPromptLayout.iconView asset. */
val iconAsset: Flow<Int> =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 34b50e4..fbd87fd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -86,7 +86,7 @@
val faceIconHeight: Int =
context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
- val fingerprintSensorDiameter: Flow<Int> =
+ val fingerprintSensorWidth: Flow<Int> =
combine(modalities, udfpsOverlayInteractor.udfpsOverlayParams) { modalities, overlayParams
->
if (modalities.hasUdfps) {
@@ -96,6 +96,16 @@
}
}
+ val fingerprintSensorHeight: Flow<Int> =
+ combine(modalities, udfpsOverlayInteractor.udfpsOverlayParams) { modalities, overlayParams
+ ->
+ if (modalities.hasUdfps) {
+ overlayParams.sensorBounds.height()
+ } else {
+ fingerprintIconHeight
+ }
+ }
+
private val _accessibilityHint = MutableSharedFlow<String>()
/** Hint for talkback directional guidance */
@@ -218,18 +228,12 @@
*/
val faceMode: Flow<Boolean> =
combine(modalities, isConfirmationRequired, fingerprintStartMode) {
- modalities: BiometricModalities,
- isConfirmationRequired: Boolean,
- fingerprintStartMode: FingerprintStartMode ->
- if (modalities.hasFaceAndFingerprint) {
- if (isConfirmationRequired) {
- false
- } else {
- !fingerprintStartMode.isStarted
- }
- } else {
- false
- }
+ modalities,
+ isConfirmationRequired,
+ fingerprintStartMode ->
+ modalities.hasFaceAndFingerprint &&
+ !isConfirmationRequired &&
+ fingerprintStartMode == FingerprintStartMode.Pending
}
.distinctUntilChanged()
@@ -249,14 +253,11 @@
* asset to be loaded before determining the prompt size.
*/
val isIconViewLoaded: Flow<Boolean> =
- combine(credentialKind, _isIconViewLoaded.asStateFlow()) { credentialKind, isIconViewLoaded
- ->
- if (credentialKind is PromptKind.Biometric) {
- isIconViewLoaded
- } else {
- true
+ combine(modalities, _isIconViewLoaded.asStateFlow()) { modalities, isIconViewLoaded ->
+ val noIcon = modalities.isEmpty
+ noIcon || isIconViewLoaded
}
- }
+ .distinctUntilChanged()
// Sets whether the prompt's iconView animation has been loaded in the view yet.
fun setIsIconViewLoaded(iconViewLoaded: Boolean) {
@@ -351,7 +352,7 @@
position,
message,
) { size, _, message ->
- size.isNotSmall && message.message.isNotBlank()
+ size.isMedium && message.message.isNotBlank()
}
/** If the auth is pending confirmation and the confirm button should be shown. */
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
index af32eb5..000f03a 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
@@ -109,7 +109,8 @@
biometricSettingsRepository.isFingerprintAuthCurrentlyAllowed.value &&
!keyguardUpdateMonitor.isFingerprintLockedOut &&
!keyguardStateController.isUnlocked &&
- !statusBarStateController.isDozing
+ !statusBarStateController.isDozing &&
+ !bouncerRepository.primaryBouncerShow.value
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 8c87b0c..893887f 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -93,6 +93,8 @@
val keyguardAuthenticatedPrimaryAuth: Flow<Int> = repository.keyguardAuthenticatedPrimaryAuth
val keyguardAuthenticatedBiometrics: Flow<Boolean> =
repository.keyguardAuthenticatedBiometrics.filterNotNull()
+ val keyguardAuthenticatedBiometricsHandled: Flow<Unit> =
+ repository.keyguardAuthenticatedBiometrics.filter { it == null }.map {}
val userRequestedBouncerWhenAlreadyAuthenticated: Flow<Int> =
repository.userRequestedBouncerWhenAlreadyAuthenticated.filterNotNull()
val isShowing: StateFlow<Boolean> = repository.primaryBouncerShow
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt
index 1c9d1f0..e1fea5f 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -23,12 +23,14 @@
import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.bouncer.ui.BouncerViewDelegate
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
/** Models UI state for the lock screen bouncer; handles user input. */
+@ExperimentalCoroutinesApi
class KeyguardBouncerViewModel
@Inject
constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 3819e61..4f4f3d0 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -97,6 +97,14 @@
}
};
+ private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
+ new KeyguardStateController.Callback() {
+ @Override
+ public void onKeyguardShowingChanged() {
+ updateSensorRegistration();
+ }
+ };
+
private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
new KeyguardUpdateMonitorCallback() {
@@ -176,6 +184,8 @@
mStatusBarStateController.addCallback(mStatusBarStateListener);
mState = mStatusBarStateController.getState();
+ mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
+
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
mJavaAdapter.alwaysCollectFlow(
@@ -364,6 +374,24 @@
} else {
sessionEnd();
}
+ updateSensorRegistration();
+ }
+
+ private boolean shouldBeRegisteredToSensors() {
+ return mScreenOn
+ && (mState == StatusBarState.KEYGUARD
+ || (mState == StatusBarState.SHADE
+ && mKeyguardStateController.isOccluded()
+ && mKeyguardStateController.isShowing()))
+ && !mShowingAod;
+ }
+
+ private void updateSensorRegistration() {
+ if (shouldBeRegisteredToSensors()) {
+ registerSensors();
+ } else {
+ unregisterSensors();
+ }
}
private void sessionStart() {
@@ -371,7 +399,6 @@
logDebug("Starting Session");
mSessionStarted = true;
mFalsingDataProvider.setJustUnlockedWithFace(false);
- registerSensors();
mFalsingDataProvider.onSessionStarted();
}
}
@@ -380,7 +407,6 @@
if (mSessionStarted) {
logDebug("Ending Session");
mSessionStarted = false;
- unregisterSensors();
mFalsingDataProvider.onSessionEnd();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index c3c7411..ef686f9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal
+import android.provider.Settings
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.CoreStartable
import com.android.systemui.communal.domain.interactor.CommunalInteractor
@@ -24,25 +25,32 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dock.DockManager
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.kotlin.emitOnStart
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import com.android.systemui.util.settings.SystemSettings
import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
/**
* A [CoreStartable] responsible for automatically navigating between communal scenes when certain
* conditions are met.
*/
-@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class CommunalSceneStartable
@Inject
@@ -50,9 +58,13 @@
private val dockManager: DockManager,
private val communalInteractor: CommunalInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val systemSettings: SystemSettings,
@Application private val applicationScope: CoroutineScope,
@Background private val bgScope: CoroutineScope,
) : CoreStartable {
+ private var screenTimeout: Int = DEFAULT_SCREEN_TIMEOUT
+
override fun start() {
// Handle automatically switching based on keyguard state.
keyguardTransitionInteractor.startedKeyguardTransitionStep
@@ -78,6 +90,49 @@
// }
// }
// .launchIn(bgScope)
+
+ systemSettings
+ .observerFlow(Settings.System.SCREEN_OFF_TIMEOUT)
+ // Read the setting value on start.
+ .emitOnStart()
+ .onEach {
+ screenTimeout =
+ systemSettings.getInt(
+ Settings.System.SCREEN_OFF_TIMEOUT,
+ DEFAULT_SCREEN_TIMEOUT
+ )
+ }
+ .launchIn(bgScope)
+
+ // Handle timing out back to the dream.
+ bgScope.launch {
+ combine(
+ communalInteractor.desiredScene,
+ // Emit a value on start so the combine starts.
+ communalInteractor.userActivity.emitOnStart()
+ ) { scene, _ ->
+ // Time out should run whenever we're dreaming and the hub is open, even if not
+ // docked.
+ scene == CommunalScenes.Communal
+ }
+ // mapLatest cancels the previous action block when new values arrive, so any
+ // already running timeout gets cancelled when conditions change or user interaction
+ // is detected.
+ .mapLatest { shouldTimeout ->
+ if (!shouldTimeout) {
+ return@mapLatest false
+ }
+
+ delay(screenTimeout.milliseconds)
+ true
+ }
+ .sample(keyguardInteractor.isDreaming, ::Pair)
+ .collect { (shouldTimeout, isDreaming) ->
+ if (isDreaming && shouldTimeout) {
+ communalInteractor.onSceneChanged(CommunalScenes.Blank)
+ }
+ }
+ }
}
private suspend fun determineSceneAfterTransition(
@@ -105,5 +160,6 @@
companion object {
val AWAKE_DEBOUNCE_DELAY = 5.seconds
val DOCK_DEBOUNCE_DELAY = 1.seconds
+ val DEFAULT_SCREEN_TIMEOUT = 15000
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 940b48c..52025b1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -63,10 +63,13 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -84,7 +87,7 @@
class CommunalInteractor
@Inject
constructor(
- @Application applicationScope: CoroutineScope,
+ @Application val applicationScope: CoroutineScope,
broadcastDispatcher: BroadcastDispatcher,
private val communalRepository: CommunalRepository,
private val widgetRepository: CommunalWidgetRepository,
@@ -152,6 +155,14 @@
/** Transition state of the hub mode. */
val transitionState: StateFlow<ObservableTransitionState> = communalRepository.transitionState
+ val _userActivity: MutableSharedFlow<Unit> =
+ MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+ val userActivity: Flow<Unit> = _userActivity.asSharedFlow()
+
+ fun signalUserInteraction() {
+ _userActivity.tryEmit(Unit)
+ }
+
/**
* Updates the transition state of the hub [SceneTransitionLayout].
*
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 85f3c20..c913300 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
@@ -45,6 +45,10 @@
val selectedKey: StateFlow<String?>
get() = _selectedKey
+ fun signalUserInteraction() {
+ communalInteractor.signalUserInteraction()
+ }
+
fun onSceneChanged(scene: SceneKey) {
communalInteractor.onSceneChanged(scene)
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index 9afd5ed..d2df276 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -24,9 +24,9 @@
import com.android.systemui.keyguard.WorkLockActivity;
import com.android.systemui.people.PeopleSpaceActivity;
import com.android.systemui.people.widget.LaunchConversationActivity;
-import com.android.systemui.screenshot.LongScreenshotActivity;
import com.android.systemui.screenshot.appclips.AppClipsActivity;
import com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity;
+import com.android.systemui.screenshot.scroll.LongScreenshotActivity;
import com.android.systemui.sensorprivacy.SensorUseStartedActivity;
import com.android.systemui.settings.brightness.BrightnessDialog;
import com.android.systemui.telephony.ui.activity.SwitchToManagedProfileForCallActivity;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
index 3d8e4cb..6aa5e8b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
@@ -22,8 +22,6 @@
import com.android.systemui.media.dialog.MediaOutputDialogReceiver;
import com.android.systemui.people.widget.PeopleSpaceWidgetPinnedReceiver;
import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
-import com.android.systemui.screenshot.ActionProxyReceiver;
-import com.android.systemui.screenshot.DeleteScreenshotReceiver;
import com.android.systemui.screenshot.SmartActionsReceiver;
import com.android.systemui.volume.VolumePanelDialogReceiver;
@@ -42,24 +40,6 @@
*/
@Binds
@IntoMap
- @ClassKey(ActionProxyReceiver.class)
- public abstract BroadcastReceiver bindActionProxyReceiver(
- ActionProxyReceiver broadcastReceiver);
-
- /**
- *
- */
- @Binds
- @IntoMap
- @ClassKey(DeleteScreenshotReceiver.class)
- public abstract BroadcastReceiver bindDeleteScreenshotReceiver(
- DeleteScreenshotReceiver broadcastReceiver);
-
- /**
- *
- */
- @Binds
- @IntoMap
@ClassKey(SmartActionsReceiver.class)
public abstract BroadcastReceiver bindSmartActionsReceiver(
SmartActionsReceiver broadcastReceiver);
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt
index 83337f7..fa7603f 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt
@@ -62,7 +62,7 @@
conflatedCallbackFlow {
val callback =
DeviceStateManager.DeviceStateCallback { state ->
- trySend(deviceStateToPosture(state))
+ trySend(deviceStateToPosture(state.identifier))
}
deviceStateManager.registerCallback(executor, callback)
awaitClose { deviceStateManager.unregisterCallback(callback) }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
index 0cb57fb..1b832d4 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
@@ -21,7 +21,6 @@
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dock.DockManager
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel
@@ -47,7 +46,6 @@
fromGlanceableHubTransitionInteractor: GlanceableHubToDreamingTransitionViewModel,
private val toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
- private val dockManager: DockManager,
private val communalInteractor: CommunalInteractor,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val userTracker: UserTracker,
@@ -55,8 +53,7 @@
fun startTransitionFromDream() {
val showGlanceableHub =
- dockManager.isDocked &&
- communalInteractor.isCommunalEnabled.value &&
+ communalInteractor.isCommunalEnabled.value &&
!keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId)
if (showGlanceableHub) {
toGlanceableHubTransitionViewModel.startTransition()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 43a8b40..3b34750 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -40,6 +40,7 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.Flags.migrateClocksToBlueprint;
import static com.android.systemui.Flags.notifyPowerManagerUserActivityBackground;
import static com.android.systemui.Flags.refactorGetCurrentUser;
import static com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.DREAMING_ANIMATION_DURATION_MS;
@@ -3401,6 +3402,11 @@
mSurfaceBehindRemoteAnimationFinishedCallback = null;
}
}
+
+ // Ensure that keyguard becomes visible if the going away animation is canceled
+ if (showKeyguard && !KeyguardWmStateRefactor.isEnabled() && migrateClocksToBlueprint()) {
+ mKeyguardInteractor.showKeyguard();
+ }
}
private void adjustStatusBarLocked() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 0e487d2..9c68c45 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -159,6 +159,11 @@
lastAnimator?.cancel()
lastAnimator = info.animator
+ // Cancel any existing manual transitions
+ updateTransitionId?.let { uuid ->
+ updateTransition(uuid, lastStep.value, TransitionState.CANCELED)
+ }
+
info.animator?.let { animator ->
// An animator was provided, so use it to run the transition
animator.setFloatValues(startingValue, 1f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 88ddfd4..47f8046a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -33,7 +33,11 @@
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
@SysUISingleton
@@ -66,6 +70,25 @@
listenForTransitionToCamera(scope, keyguardInteractor)
}
+ val surfaceBehindVisibility: Flow<Boolean?> =
+ combine(
+ transitionInteractor.startedKeyguardTransitionStep,
+ transitionInteractor.transitionStepsFromState(KeyguardState.ALTERNATE_BOUNCER)
+ ) { startedStep, fromBouncerStep ->
+ if (startedStep.to != KeyguardState.GONE) {
+ return@combine null
+ }
+
+ // The alt bouncer is pretty fast to hide, so start the surface behind animation
+ // around 30%.
+ fromBouncerStep.value > 0.3f
+ }
+ .onStart {
+ // Default to null ("don't care, use a reasonable default").
+ emit(null)
+ }
+ .distinctUntilChanged()
+
private fun listenForAlternateBouncerToLockscreenHubAodOrDozing() {
scope.launch {
keyguardInteractor.alternateBouncerShowing
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index d5a9bd1..4a3232e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -71,6 +71,10 @@
listenForGoneToDreamingLockscreenHosted()
}
+ fun showKeyguard() {
+ scope.launch { startTransitionTo(KeyguardState.LOCKSCREEN) }
+ }
+
// Primarily for when the user chooses to lock down the device
private fun listenForGoneToLockscreenOrHub() {
if (KeyguardWmStateRefactor.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 391dccc..c7fafba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -26,7 +26,6 @@
import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -96,36 +95,6 @@
}
.distinctUntilChanged()
- val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> =
- combine(
- transitionInteractor.startedKeyguardTransitionStep,
- transitionInteractor.transitionStepsFromState(KeyguardState.PRIMARY_BOUNCER)
- ) { startedStep, fromBouncerStep ->
- if (startedStep.to != KeyguardState.GONE) {
- // BOUNCER to anything but GONE does not require any special surface
- // visibility handling.
- return@combine null
- }
-
- if (fromBouncerStep.value > 0.5f) {
- KeyguardSurfaceBehindModel(
- animateFromAlpha = 0f,
- alpha = 1f,
- animateFromTranslationY = 500f,
- translationY = 0f,
- )
- } else {
- KeyguardSurfaceBehindModel(
- alpha = 0f,
- )
- }
- }
- .onStart {
- // Default to null ("don't care, use a reasonable default").
- emit(null)
- }
- .distinctUntilChanged()
-
fun dismissPrimaryBouncer() {
scope.launch { startTransitionTo(KeyguardState.GONE) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 143edf9..283f160 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -57,6 +57,7 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
@@ -82,6 +83,7 @@
shadeRepository: ShadeRepository,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
sceneInteractorProvider: Provider<SceneInteractor>,
+ private val fromGoneTransitionInteractor: Provider<FromGoneTransitionInteractor>,
) {
// TODO(b/296118689): move to a repository
private val _sharedNotificationContainerBounds = MutableStateFlow(NotificationContainerBounds())
@@ -179,11 +181,19 @@
/** Keyguard can be clipped at the top as the shade is dragged */
val topClippingBounds: Flow<Int?> =
- combine(configurationInteractor.onAnyConfigurationChange, repository.topClippingBounds) {
- _,
- topClippingBounds ->
- topClippingBounds
- }
+ combineTransform(
+ configurationInteractor.onAnyConfigurationChange,
+ keyguardTransitionInteractor
+ .transitionValue(GONE)
+ .map { it == 1f }
+ .onStart { emit(false) },
+ repository.topClippingBounds
+ ) { _, isGone, topClippingBounds ->
+ if (!isGone) {
+ emit(topClippingBounds)
+ }
+ }
+ .distinctUntilChanged()
/** Last point that [KeyguardRootView] view was tapped */
val lastRootViewTapPosition: Flow<Point?> = repository.lastRootViewTapPosition.asStateFlow()
@@ -374,6 +384,11 @@
repository.topClippingBounds.value = top
}
+ /** Temporary shim, until [KeyguardWmStateRefactor] is enabled */
+ fun showKeyguard() {
+ fromGoneTransitionInteractor.get().showKeyguard()
+ }
+
companion object {
private const val TAG = "KeyguardInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
index c496a6e..80e94a2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
@@ -95,7 +95,17 @@
}
.distinctUntilChanged()
- val isAnimatingSurface = repository.isAnimatingSurface
+ /**
+ * Whether we're animating the surface, or a notification launch animation is running (which
+ * means we're going to animate the surface, even if animators aren't yet running).
+ */
+ val isAnimatingSurface =
+ combine(
+ repository.isAnimatingSurface,
+ notificationLaunchInteractor.isLaunchAnimationRunning
+ ) { animatingSurface, animatingLaunch ->
+ animatingSurface || animatingLaunch
+ }
fun setAnimatingSurface(animating: Boolean) {
repository.setAnimatingSurface(animating)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index c28e49d..68ea5d0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -18,7 +18,7 @@
import com.android.keyguard.logging.KeyguardLogger
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.core.LogLevel.VERBOSE
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -34,7 +34,7 @@
class KeyguardTransitionAuditLogger
@Inject
constructor(
- @Application private val scope: CoroutineScope,
+ @Background private val scope: CoroutineScope,
private val interactor: KeyguardTransitionInteractor,
private val keyguardInteractor: KeyguardInteractor,
private val logger: KeyguardLogger,
@@ -70,6 +70,12 @@
}
scope.launch {
+ sharedNotificationContainerViewModel.bounds.collect {
+ logger.log(TAG, VERBOSE, "Notif: bounds", it)
+ }
+ }
+
+ scope.launch {
sharedNotificationContainerViewModel.isOnLockscreenWithoutShade.collect {
logger.log(TAG, VERBOSE, "Notif: isOnLockscreenWithoutShade", it)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 00902b4..e6655ee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -20,6 +20,7 @@
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -38,9 +39,12 @@
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -50,6 +54,7 @@
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
/** Encapsulates business-logic related to the keyguard transitions. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -58,6 +63,7 @@
@Inject
constructor(
@Application val scope: CoroutineScope,
+ @Main private val mainDispatcher: CoroutineDispatcher,
private val keyguardRepository: KeyguardRepository,
private val repository: KeyguardTransitionRepository,
private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
@@ -70,6 +76,30 @@
) {
private val TAG = this::class.simpleName
+ private val transitionValueCache = mutableMapOf<KeyguardState, MutableSharedFlow<Float>>()
+
+ /**
+ * Numerous flows are derived from, or care directly about, the transition value in and out of a
+ * single state. This prevent the redundant filters from running.
+ */
+ private fun getTransitionValueFlow(state: KeyguardState): MutableSharedFlow<Float> {
+ return transitionValueCache.getOrPut(state) {
+ MutableSharedFlow<Float>(
+ extraBufferCapacity = 2,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST
+ )
+ }
+ }
+
+ init {
+ scope.launch(mainDispatcher) {
+ repository.transitions.collect { step ->
+ getTransitionValueFlow(step.from).emit(1f - step.value)
+ getTransitionValueFlow(step.to).emit(step.value)
+ }
+ }
+ }
+
/** (any)->GONE transition information */
val anyStateToGoneTransition: Flow<TransitionStep> =
repository.transitions.filter { step -> step.to == GONE }
@@ -353,15 +383,7 @@
fun transitionValue(
state: KeyguardState,
): Flow<Float> {
- return repository.transitions
- .filter { it.from == state || it.to == state }
- .map {
- if (it.from == state) {
- 1 - it.value
- } else {
- it.value
- }
- }
+ return getTransitionValueFlow(state)
}
fun transitionStepsFromState(fromState: KeyguardState): Flow<TransitionStep> {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index cff74b3..8d02e0e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -40,6 +40,7 @@
surfaceBehindInteractor: KeyguardSurfaceBehindInteractor,
fromLockscreenInteractor: FromLockscreenTransitionInteractor,
fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
+ fromAlternateBouncerInteractor: FromAlternateBouncerTransitionInteractor,
notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
) {
private val defaultSurfaceBehindVisibility =
@@ -65,6 +66,9 @@
KeyguardState.PRIMARY_BOUNCER -> {
fromBouncerInteractor.surfaceBehindVisibility
}
+ KeyguardState.ALTERNATE_BOUNCER -> {
+ fromAlternateBouncerInteractor.surfaceBehindVisibility
+ }
else -> flowOf(null)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BurnInModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BurnInModel.kt
index 02d1471..0c8ddee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BurnInModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BurnInModel.kt
@@ -20,6 +20,6 @@
data class BurnInModel(
val translationX: Int = 0,
val translationY: Int = 0,
- val scale: Float = 0f,
+ val scale: Float = 1f,
val scaleClockOnly: Boolean = false,
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
index d400210..3a2781c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
@@ -102,9 +102,16 @@
launch {
viewModel.scrimAlpha.collect {
+ val wasVisible = alternateBouncerViewContainer.visibility == View.VISIBLE
alternateBouncerViewContainer.visibility =
if (it < .1f) View.INVISIBLE else View.VISIBLE
scrim.viewAlpha = it
+ if (
+ wasVisible && alternateBouncerViewContainer.visibility == View.INVISIBLE
+ ) {
+ // view is no longer visible
+ viewModel.hideAlternateBouncer()
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
index 1abf4a6..f20c4ac 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
@@ -21,8 +21,10 @@
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToPrimaryBouncerTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToPrimaryBouncerTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel
@@ -73,13 +75,17 @@
@Binds
@IntoSet
+ abstract fun aodToGone(impl: AodToGoneTransitionViewModel): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
abstract fun aodToLockscreen(
impl: AodToLockscreenTransitionViewModel
): DeviceEntryIconTransition
@Binds
@IntoSet
- abstract fun aodToGone(impl: AodToGoneTransitionViewModel): DeviceEntryIconTransition
+ abstract fun aodToOccluded(impl: AodToOccludedTransitionViewModel): DeviceEntryIconTransition
@Binds
@IntoSet
@@ -93,6 +99,12 @@
@Binds
@IntoSet
+ abstract fun dozingToOccluded(
+ impl: DozingToOccludedTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
abstract fun dozingToPrimaryBouncer(
impl: DozingToPrimaryBouncerTransitionViewModel
): DeviceEntryIconTransition
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index 1085f94..8fd8bec 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -29,6 +29,7 @@
import com.android.keyguard.LockIconView
import com.android.keyguard.LockIconViewController
import com.android.systemui.Flags.keyguardBottomAreaRefactor
+import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
@@ -70,7 +71,11 @@
private val deviceEntryIconViewId = R.id.device_entry_icon_view
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!keyguardBottomAreaRefactor() && !DeviceEntryUdfpsRefactor.isEnabled) {
+ if (
+ !keyguardBottomAreaRefactor() &&
+ !migrateClocksToBlueprint() &&
+ !DeviceEntryUdfpsRefactor.isEnabled
+ ) {
return
}
@@ -82,7 +87,7 @@
if (DeviceEntryUdfpsRefactor.isEnabled) {
DeviceEntryIconView(context, null).apply { id = deviceEntryIconViewId }
} else {
- // keyguardBottomAreaRefactor()
+ // keyguardBottomAreaRefactor() or migrateClocksToBlueprint()
LockIconView(context, null).apply { id = R.id.lock_icon_view }
}
constraintLayout.addView(view)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
index 2526f0a..10a9e3b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
@@ -89,4 +89,8 @@
fun showPrimaryBouncer() {
statusBarKeyguardViewManager.showPrimaryBouncer(/* scrimmed */ true)
}
+
+ fun hideAlternateBouncer() {
+ statusBarKeyguardViewManager.hideAlternateBouncer(false)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
index abf2372..6042117 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
@@ -72,7 +72,11 @@
duration = TO_LOCKSCREEN_DURATION,
onStep = { value -> -translatePx + value * translatePx },
interpolator = EMPHASIZED,
- onCancel = { -translatePx.toFloat() },
+ // Move notifications back to their original position since they can be
+ // accessed from the shade, and also keyguard elements in case the animation
+ // is cancelled.
+ onFinish = { 0f },
+ onCancel = { 0f },
name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardTranslationX"
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 5ca9215..41cc1d6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -56,6 +56,7 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
@@ -125,13 +126,36 @@
.onStart { emit(false) }
.distinctUntilChanged()
- private val alphaOnShadeExpansion: Flow<Float> =
+ private val isOnLockscreen: Flow<Boolean> =
combine(
+ keyguardTransitionInteractor.isFinishedInState(LOCKSCREEN).onStart { emit(false) },
+ keyguardTransitionInteractor
+ .isInTransitionWhere { from, to -> from == LOCKSCREEN || to == LOCKSCREEN }
+ .onStart { emit(false) }
+ ) { onLockscreen, transitioningToOrFromLockscreen ->
+ onLockscreen || transitioningToOrFromLockscreen
+ }
+ .distinctUntilChanged()
+
+ private val alphaOnShadeExpansion: Flow<Float> =
+ combineTransform(
+ isOnLockscreen,
shadeInteractor.qsExpansion,
shadeInteractor.shadeExpansion,
- ) { qsExpansion, shadeExpansion ->
+ ) { isOnLockscreen, qsExpansion, shadeExpansion ->
// Fade out quickly as the shade expands
- 1f - MathUtils.constrainedMap(0f, 1f, 0f, 0.2f, max(qsExpansion, shadeExpansion))
+ if (isOnLockscreen) {
+ val alpha =
+ 1f -
+ MathUtils.constrainedMap(
+ /* rangeMin = */ 0f,
+ /* rangeMax = */ 1f,
+ /* valueMin = */ 0f,
+ /* valueMax = */ 0.2f,
+ /* value = */ max(qsExpansion, shadeExpansion)
+ )
+ emit(alpha)
+ }
}
.distinctUntilChanged()
@@ -235,11 +259,7 @@
burnInJob?.cancel()
burnInJob =
- scope.launch {
- aodBurnInViewModel.movement(params).collect {
- burnInModel.value = it
- }
- }
+ scope.launch { aodBurnInViewModel.movement(params).collect { burnInModel.value = it } }
}
val scale: Flow<BurnInScaleViewModel> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
index b7f7b06..5cbc1d4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
@@ -71,7 +71,8 @@
duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
onStep = { value -> value * translatePx },
// Move notifications back to their original position since they can be
- // accessed from the shade.
+ // accessed from the shade, and also keyguard elements in case the animation
+ // is cancelled.
onFinish = { 0f },
onCancel = { 0f },
interpolator = EMPHASIZED,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index 730aa62..5dde14b 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -65,7 +65,7 @@
it.topActivity,
it.baseIntent?.component,
it.taskDescription?.backgroundColor,
- isForegroundTask = it.taskId in foregroundTaskIds
+ isForegroundTask = it.taskId in foregroundTaskIds && it.isVisible
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index f4903f1..768bb8e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -131,6 +131,7 @@
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.rotation.RotationButton;
@@ -199,6 +200,7 @@
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
private final KeyguardStateController mKeyguardStateController;
private final ShadeViewController mShadeViewController;
+ private final PanelExpansionInteractor mPanelExpansionInteractor;
private final NotificationRemoteInputManager mNotificationRemoteInputManager;
private final OverviewProxyService mOverviewProxyService;
private final NavigationModeController mNavigationModeController;
@@ -537,6 +539,7 @@
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
KeyguardStateController keyguardStateController,
ShadeViewController shadeViewController,
+ PanelExpansionInteractor panelExpansionInteractor,
NotificationRemoteInputManager notificationRemoteInputManager,
NotificationShadeDepthController notificationShadeDepthController,
@Main Handler mainHandler,
@@ -575,6 +578,7 @@
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
mKeyguardStateController = keyguardStateController;
mShadeViewController = shadeViewController;
+ mPanelExpansionInteractor = panelExpansionInteractor;
mNotificationRemoteInputManager = notificationRemoteInputManager;
mOverviewProxyService = overviewProxyService;
mNavigationModeController = navigationModeController;
@@ -749,7 +753,7 @@
final Display display = mView.getDisplay();
mView.setComponents(mRecentsOptional);
if (mCentralSurfacesOptionalLazy.get().isPresent()) {
- mView.setComponents(mShadeViewController);
+ mView.setComponents(mShadeViewController, mPanelExpansionInteractor);
}
mView.setDisabledFlags(mDisabledFlags1, mSysUiFlagsContainer);
mView.setOnVerticalChangedListener(this::onVerticalChanged);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index c5190a2..1927f49 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -75,6 +75,7 @@
import com.android.systemui.res.R;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shared.rotation.FloatingRotationButton;
import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback;
import com.android.systemui.shared.rotation.RotationButtonController;
@@ -149,7 +150,9 @@
private NavigationBarInflaterView mNavigationInflaterView;
private Optional<Recents> mRecentsOptional = Optional.empty();
@Nullable
- private ShadeViewController mPanelView;
+ private ShadeViewController mShadeViewController;
+ @Nullable
+ private PanelExpansionInteractor mPanelExpansionInteractor;
private RotationContextButton mRotationContextButton;
private FloatingRotationButton mFloatingRotationButton;
private RotationButtonController mRotationButtonController;
@@ -347,8 +350,9 @@
}
/** */
- public void setComponents(ShadeViewController panel) {
- mPanelView = panel;
+ public void setComponents(ShadeViewController svc, PanelExpansionInteractor pei) {
+ mShadeViewController = svc;
+ mPanelExpansionInteractor = pei;
updatePanelSystemUiStateFlags();
}
@@ -750,10 +754,10 @@
private void updatePanelSystemUiStateFlags() {
if (SysUiState.DEBUG) {
- Log.d(TAG, "Updating panel sysui state flags: panelView=" + mPanelView);
+ Log.d(TAG, "Updating panel sysui state flags: panelView=" + mShadeViewController);
}
- if (mPanelView != null) {
- mPanelView.updateSystemUiStateFlags();
+ if (mShadeViewController != null) {
+ mShadeViewController.updateSystemUiStateFlags();
}
}
@@ -801,7 +805,8 @@
*/
void updateSlippery() {
setSlippery(!isQuickStepSwipeUpEnabled() ||
- (mPanelView != null && mPanelView.isFullyExpanded() && !mPanelView.isCollapsing()));
+ (mPanelExpansionInteractor != null && mPanelExpansionInteractor.isFullyExpanded()
+ && !mPanelExpansionInteractor.isCollapsing()));
}
void setSlippery(boolean slippery) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index c657b55..9c88eb9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -17,6 +17,8 @@
import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static com.android.systemui.Flags.centralizedStatusBarHeightFix;
+
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -27,6 +29,7 @@
import android.widget.FrameLayout;
import com.android.systemui.res.R;
+import com.android.systemui.shade.LargeScreenHeaderHelper;
import com.android.systemui.util.LargeScreenUtils;
/**
@@ -97,7 +100,9 @@
qqsLP.topMargin = mContext.getResources()
.getDimensionPixelSize(R.dimen.qqs_layout_margin_top);
} else {
- qqsLP.topMargin = mContext.getResources()
+ qqsLP.topMargin = centralizedStatusBarHeightFix()
+ ? LargeScreenHeaderHelper.getLargeScreenHeaderHeight(mContext)
+ : mContext.getResources()
.getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height);
}
mHeaderQsPanel.setLayoutParams(qqsLP);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
index aed08f8..4a8e33a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
@@ -37,7 +37,11 @@
data class PlatformTileSpec
internal constructor(
override val spec: String,
- ) : TileSpec(spec)
+ ) : TileSpec(spec) {
+ override fun toString(): String {
+ return "P($spec)"
+ }
+ }
/**
* Container for the spec of a tile provided by an app.
@@ -50,7 +54,7 @@
val componentName: ComponentName,
) : TileSpec(spec) {
override fun toString(): String {
- return "CustomTileSpec(${componentName.toShortString()})"
+ return "C(${componentName.flattenToShortString()})"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index bd66843..d82b175 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -41,6 +41,7 @@
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.recordissue.IssueRecordingService
import com.android.systemui.recordissue.RecordIssueDialogDelegate
@@ -66,6 +67,7 @@
private val keyguardDismissUtil: KeyguardDismissUtil,
private val keyguardStateController: KeyguardStateController,
private val dialogTransitionAnimator: DialogTransitionAnimator,
+ private val panelInteractor: PanelInteractor,
private val userContextProvider: UserContextProvider,
private val delegateFactory: RecordIssueDialogDelegate.Factory,
) :
@@ -138,6 +140,8 @@
.create {
isRecording = true
startIssueRecordingService(it.screenRecord, it.winscopeTracing)
+ dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations()
+ panelInteractor.collapsePanels()
refreshState()
}
.createDialog()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt
index dcae088..1247854 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt
@@ -30,7 +30,7 @@
private val bluetoothAutoOnRepository: BluetoothAutoOnRepository,
) {
- val isEnabled = bluetoothAutoOnRepository.getValue.map { it == ENABLED }.distinctUntilChanged()
+ val isEnabled = bluetoothAutoOnRepository.isAutoOn.map { it == ENABLED }.distinctUntilChanged()
/**
* Checks if the auto on value is present in the repository.
@@ -49,7 +49,7 @@
Log.e(TAG, "Trying to set toggle value while feature not available.")
} else {
val newValue = if (value) ENABLED else DISABLED
- bluetoothAutoOnRepository.setValue(newValue)
+ bluetoothAutoOnRepository.setAutoOn(newValue)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt
index e17b4d3..f97fc38 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt
@@ -16,8 +16,6 @@
package com.android.systemui.qs.tiles.dialog.bluetooth
-import android.os.UserHandle
-import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -27,17 +25,21 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
-/** Repository class responsible for managing the Bluetooth Auto-On feature settings. */
-// TODO(b/316822488): Handle multi-user
+/**
+ * Repository class responsible for managing the Bluetooth Auto-On feature settings for the current
+ * user.
+ */
@SysUISingleton
class BluetoothAutoOnRepository
@Inject
@@ -47,21 +49,24 @@
@Application private val coroutineScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
) {
- // Flow representing the auto on setting value
- internal val getValue: Flow<Int> =
- secureSettings
- .observerFlow(UserHandle.USER_SYSTEM, SETTING_NAME)
- .onStart { emit(Unit) }
- .map {
- if (userRepository.getSelectedUserInfo().id != UserHandle.USER_SYSTEM) {
- Log.i(TAG, "Current user is not USER_SYSTEM. Multi-user is not supported")
- return@map UNSET
- }
- secureSettings.getIntForUser(SETTING_NAME, UNSET, UserHandle.USER_SYSTEM)
+
+ // Flow representing the auto on setting value for the current user
+ @OptIn(ExperimentalCoroutinesApi::class)
+ internal val isAutoOn: StateFlow<Int> =
+ userRepository.selectedUserInfo
+ .flatMapLatest { userInfo ->
+ secureSettings
+ .observerFlow(userInfo.id, SETTING_NAME)
+ .onStart { emit(Unit) }
+ .map { secureSettings.getIntForUser(SETTING_NAME, UNSET, userInfo.id) }
}
.distinctUntilChanged()
.flowOn(backgroundDispatcher)
- .shareIn(coroutineScope, SharingStarted.WhileSubscribed(replayExpirationMillis = 0))
+ .stateIn(
+ coroutineScope,
+ SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
+ UNSET
+ )
/**
* Checks if the auto on setting value is ever set for the current user.
@@ -70,12 +75,11 @@
*/
suspend fun isValuePresent(): Boolean =
withContext(backgroundDispatcher) {
- if (userRepository.getSelectedUserInfo().id != UserHandle.USER_SYSTEM) {
- Log.i(TAG, "Current user is not USER_SYSTEM. Multi-user is not supported")
- false
- } else {
- secureSettings.getIntForUser(SETTING_NAME, UNSET, UserHandle.USER_SYSTEM) != UNSET
- }
+ secureSettings.getIntForUser(
+ SETTING_NAME,
+ UNSET,
+ userRepository.getSelectedUserInfo().id
+ ) != UNSET
}
/**
@@ -83,18 +87,17 @@
*
* @param value The new setting value to be applied.
*/
- suspend fun setValue(value: Int) {
+ suspend fun setAutoOn(value: Int) {
withContext(backgroundDispatcher) {
- if (userRepository.getSelectedUserInfo().id != UserHandle.USER_SYSTEM) {
- Log.i(TAG, "Current user is not USER_SYSTEM. Multi-user is not supported")
- } else {
- secureSettings.putIntForUser(SETTING_NAME, value, UserHandle.USER_SYSTEM)
- }
+ secureSettings.putIntForUser(
+ SETTING_NAME,
+ value,
+ userRepository.getSelectedUserInfo().id
+ )
}
}
companion object {
- private const val TAG = "BluetoothAutoOnRepository"
const val SETTING_NAME = "bluetooth_automatic_turn_on"
const val UNSET = -1
}
diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
index 9076182..f02b871 100644
--- a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
@@ -16,12 +16,14 @@
package com.android.systemui.reardisplay;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateManagerGlobal;
import android.view.LayoutInflater;
@@ -29,7 +31,6 @@
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
@@ -179,6 +180,7 @@
*/
private void initializeValues(int startingBaseState) {
mRearDisplayEducationDialog = mSystemUIDialogFactory.create();
+ // TODO(b/329170810): Refactor and remove with updated DeviceStateManager values.
if (mFoldedStates == null) {
mFoldedStates = mResources.getIntArray(
com.android.internal.R.array.config_foldedDeviceStates);
@@ -228,21 +230,19 @@
private class DeviceStateManagerCallback implements DeviceStateManager.DeviceStateCallback {
@Override
- public void onBaseStateChanged(int state) {
- if (mStartedFolded && !isFoldedState(state)) {
+ public void onDeviceStateChanged(@NonNull DeviceState state) {
+ if (mStartedFolded && !state.hasProperty(
+ DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED)) {
// We've opened the device, we can close the overlay
mRearDisplayEducationDialog.dismiss();
closeOverlayAndNotifyService(false);
- } else if (!mStartedFolded && isFoldedState(state)) {
+ } else if (!mStartedFolded && state.hasProperty(
+ DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED)) {
// We've closed the device, finish activity
mRearDisplayEducationDialog.dismiss();
closeOverlayAndNotifyService(true);
}
}
-
- // We only care about physical device changes in this scenario
- @Override
- public void onStateChanged(int state) {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 8a84496..7009816 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -27,8 +27,10 @@
import android.util.Log
import androidx.core.content.FileProvider
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.dagger.qualifiers.LongRunning
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
import com.android.systemui.res.R
import com.android.systemui.screenrecord.RecordingController
import com.android.systemui.screenrecord.RecordingService
@@ -54,7 +56,9 @@
uiEventLogger: UiEventLogger,
notificationManager: NotificationManager,
userContextProvider: UserContextProvider,
- keyguardDismissUtil: KeyguardDismissUtil
+ keyguardDismissUtil: KeyguardDismissUtil,
+ private val dialogTransitionAnimator: DialogTransitionAnimator,
+ private val panelInteractor: PanelInteractor,
) :
RecordingService(
controller,
@@ -102,6 +106,8 @@
}
ACTION_SHARE -> {
shareRecording(intent)
+ dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations()
+ panelInteractor.collapsePanels()
// Unlike all other actions, action_share has different behavior for the screen
// recording qs tile than it does for the record issue qs tile. Return sticky to
@@ -124,13 +130,11 @@
FileSender.buildSendIntent(this, listOf(sharableUri))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- if (mNotificationId != NOTIF_BASE_ID) {
- mNotificationManager.cancelAsUser(
- null,
- mNotificationId,
- UserHandle(mUserContextTracker.userContext.userId)
- )
- }
+ mNotificationManager.cancelAsUser(
+ null,
+ mNotificationId,
+ UserHandle(mUserContextTracker.userContext.userId)
+ )
// TODO: Debug why the notification shade isn't closing upon starting the BetterBug activity
mKeyguardDismissUtil.executeWhenUnlocked(
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index ff18a11..7313a49 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -28,8 +28,8 @@
import android.widget.Button
import android.widget.PopupMenu
import android.widget.Switch
-import androidx.annotation.AnyThread
import androidx.annotation.MainThread
+import androidx.annotation.WorkerThread
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlagsClassic
@@ -74,6 +74,7 @@
@SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var screenRecordSwitch: Switch
private lateinit var issueTypeButton: Button
+ private var hasSelectedIssueType: Boolean = false
@MainThread
override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
@@ -82,15 +83,21 @@
setTitle(context.getString(R.string.qs_record_issue_label))
setIcon(R.drawable.qs_record_issue_icon_off)
setNegativeButton(R.string.cancel) { _, _ -> dismiss() }
- setPositiveButton(R.string.qs_record_issue_start) { _, _ ->
- onStarted.accept(
- IssueRecordingConfig(
- screenRecordSwitch.isChecked,
- true /* TODO: Base this on issueType selected */
- )
- )
- dismiss()
- }
+ setPositiveButton(
+ R.string.qs_record_issue_start,
+ { _, _ ->
+ if (hasSelectedIssueType) {
+ onStarted.accept(
+ IssueRecordingConfig(
+ screenRecordSwitch.isChecked,
+ true /* TODO: Base this on issueType selected */
+ )
+ )
+ dismiss()
+ }
+ },
+ false
+ )
}
}
@@ -104,49 +111,47 @@
screenRecordSwitch = requireViewById(R.id.screenrecord_switch)
screenRecordSwitch.setOnCheckedChangeListener { _, isEnabled ->
- onScreenRecordSwitchClicked(context, isEnabled)
+ if (isEnabled) {
+ bgExecutor.execute { onScreenRecordSwitchClicked() }
+ }
}
issueTypeButton = requireViewById(R.id.issue_type_button)
issueTypeButton.setOnClickListener { onIssueTypeClicked(context) }
}
}
- @AnyThread
- private fun onScreenRecordSwitchClicked(context: Context, isEnabled: Boolean) {
- if (!isEnabled) return
-
- bgExecutor.execute {
- if (
- flags.isEnabled(WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES) &&
- devicePolicyResolver
- .get()
- .isScreenCaptureCompletelyDisabled(UserHandle.of(userTracker.userId))
- ) {
- mainExecutor.execute {
- screenCaptureDisabledDialogDelegate.createDialog().show()
- screenRecordSwitch.isChecked = false
- }
- return@execute
+ @WorkerThread
+ private fun onScreenRecordSwitchClicked() {
+ if (
+ flags.isEnabled(WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES) &&
+ devicePolicyResolver
+ .get()
+ .isScreenCaptureCompletelyDisabled(UserHandle.of(userTracker.userId))
+ ) {
+ mainExecutor.execute {
+ screenCaptureDisabledDialogDelegate.createDialog().show()
+ screenRecordSwitch.isChecked = false
}
+ return
+ }
- mediaProjectionMetricsLogger.notifyProjectionInitiated(
- userTracker.userId,
- SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER
- )
+ mediaProjectionMetricsLogger.notifyProjectionInitiated(
+ userTracker.userId,
+ SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER
+ )
- if (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)) {
- val prefs =
- userFileManager.getSharedPreferences(
- RecordIssueTile.TILE_SPEC,
- Context.MODE_PRIVATE,
- userTracker.userId
- )
- if (!prefs.getBoolean(HAS_APPROVED_SCREEN_RECORDING, false)) {
- mainExecutor.execute {
- ScreenCapturePermissionDialogDelegate(factory, prefs).createDialog().apply {
- setOnCancelListener { screenRecordSwitch.isChecked = false }
- show()
- }
+ if (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)) {
+ val prefs =
+ userFileManager.getSharedPreferences(
+ RecordIssueTile.TILE_SPEC,
+ Context.MODE_PRIVATE,
+ userTracker.userId
+ )
+ if (!prefs.getBoolean(HAS_APPROVED_SCREEN_RECORDING, false)) {
+ mainExecutor.execute {
+ ScreenCapturePermissionDialogDelegate(factory, prefs).createDialog().apply {
+ setOnCancelListener { screenRecordSwitch.isChecked = false }
+ show()
}
}
}
@@ -174,5 +179,6 @@
setForceShowIcon(true)
show()
}
+ hasSelectedIssueType = true
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index 1808d98..467089d 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
@@ -19,9 +19,11 @@
package com.android.systemui.scene.shared.flag
import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
+import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.Flags.keyguardBottomAreaRefactor
+import com.android.systemui.Flags.keyguardWmStateRefactor
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.Flags.sceneContainer
import com.android.systemui.dagger.SysUISingleton
@@ -46,7 +48,8 @@
keyguardBottomAreaRefactor() &&
migrateClocksToBlueprint() &&
ComposeLockscreen.isEnabled &&
- MediaInSceneContainerFlag.isEnabled
+ MediaInSceneContainerFlag.isEnabled &&
+ keyguardWmStateRefactor()
// NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer
/**
@@ -65,6 +68,7 @@
sequenceOf(
FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor()),
FlagToken(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, migrateClocksToBlueprint()),
+ FlagToken(FLAG_KEYGUARD_WM_STATE_REFACTOR, keyguardWmStateRefactor()),
ComposeLockscreen.token,
MediaInSceneContainerFlag.token,
// NOTE: Changes should also be made in isEnabled and @EnableSceneContainer
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index ac94f39..b2c01e1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -82,7 +82,7 @@
private final RecordingController mController;
protected final KeyguardDismissUtil mKeyguardDismissUtil;
private final Handler mMainHandler;
- private ScreenRecordingAudioSource mAudioSource;
+ private ScreenRecordingAudioSource mAudioSource = ScreenRecordingAudioSource.NONE;
private boolean mShowTaps;
private boolean mOriginalShowTaps;
private ScreenMediaRecorder mRecorder;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
deleted file mode 100644
index 7e234ae..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2020 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.screenshot;
-
-import static com.android.systemui.screenshot.ScreenshotController.ACTION_TYPE_EDIT;
-import static com.android.systemui.screenshot.ScreenshotController.ACTION_TYPE_SHARE;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_INTENT;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_DISALLOW_ENTER_PIP;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED;
-import static com.android.systemui.statusbar.phone.CentralSurfaces.SYSTEM_DIALOG_REASON_SCREENSHOT;
-
-import android.app.ActivityOptions;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-import android.view.RemoteAnimationAdapter;
-import android.view.WindowManagerGlobal;
-
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.settings.DisplayTracker;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-
-import javax.inject.Inject;
-
-/**
- * Receiver to proxy the share or edit intent, used to clean up the notification and send
- * appropriate signals to the system (ie. to dismiss the keyguard if necessary).
- */
-public class ActionProxyReceiver extends BroadcastReceiver {
- private static final String TAG = "ActionProxyReceiver";
-
- private final ActivityManagerWrapper mActivityManagerWrapper;
- private final ScreenshotSmartActions mScreenshotSmartActions;
- private final DisplayTracker mDisplayTracker;
- private final ActivityStarter mActivityStarter;
-
- @Inject
- public ActionProxyReceiver(ActivityManagerWrapper activityManagerWrapper,
- ScreenshotSmartActions screenshotSmartActions,
- DisplayTracker displayTracker,
- ActivityStarter activityStarter) {
- mActivityManagerWrapper = activityManagerWrapper;
- mScreenshotSmartActions = screenshotSmartActions;
- mDisplayTracker = displayTracker;
- mActivityStarter = activityStarter;
- }
-
- @Override
- public void onReceive(Context context, final Intent intent) {
- Runnable startActivityRunnable = () -> {
- mActivityManagerWrapper.closeSystemWindows(SYSTEM_DIALOG_REASON_SCREENSHOT);
-
- PendingIntent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
- ActivityOptions opts = ActivityOptions.makeBasic();
- opts.setDisallowEnterPictureInPictureWhileLaunching(
- intent.getBooleanExtra(EXTRA_DISALLOW_ENTER_PIP, false));
- opts.setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
- try {
- actionIntent.send(context, 0, null, null, null, null, opts.toBundle());
- if (intent.getBooleanExtra(ScreenshotController.EXTRA_OVERRIDE_TRANSITION, false)) {
- RemoteAnimationAdapter runner = new RemoteAnimationAdapter(
- ScreenshotController.SCREENSHOT_REMOTE_RUNNER, 0, 0);
- try {
- WindowManagerGlobal.getWindowManagerService()
- .overridePendingAppTransitionRemote(runner,
- mDisplayTracker.getDefaultDisplayId());
- } catch (Exception e) {
- Log.e(TAG, "Error overriding screenshot app transition", e);
- }
- }
- } catch (PendingIntent.CanceledException e) {
- Log.e(TAG, "Pending intent canceled", e);
- }
-
- };
-
- mActivityStarter.executeRunnableDismissingKeyguard(startActivityRunnable, null,
- true /* dismissShade */, true /* afterKeyguardGone */,
- true /* deferred */);
-
- if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
- String actionType = Intent.ACTION_EDIT.equals(intent.getAction())
- ? ACTION_TYPE_EDIT
- : ACTION_TYPE_SHARE;
- mScreenshotSmartActions.notifyScreenshotAction(
- intent.getStringExtra(EXTRA_ID), actionType, false, null);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java
deleted file mode 100644
index e0346f2..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2020 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.screenshot;
-
-import static com.android.systemui.screenshot.ScreenshotController.ACTION_TYPE_DELETE;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED;
-import static com.android.systemui.screenshot.ScreenshotController.SCREENSHOT_URI_ID;
-
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-
-import com.android.systemui.dagger.qualifiers.Background;
-
-import java.util.concurrent.Executor;
-
-import javax.inject.Inject;
-
-/**
- * Removes the file at a provided URI.
- */
-public class DeleteScreenshotReceiver extends BroadcastReceiver {
-
- private final ScreenshotSmartActions mScreenshotSmartActions;
- private final Executor mBackgroundExecutor;
-
- @Inject
- public DeleteScreenshotReceiver(ScreenshotSmartActions screenshotSmartActions,
- @Background Executor backgroundExecutor) {
- mScreenshotSmartActions = screenshotSmartActions;
- mBackgroundExecutor = backgroundExecutor;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (!intent.hasExtra(SCREENSHOT_URI_ID)) {
- return;
- }
-
- // And delete the image from the media store
- final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID));
- mBackgroundExecutor.execute(() -> {
- ContentResolver resolver = context.getContentResolver();
- resolver.delete(uri, null, null);
- });
- if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
- mScreenshotSmartActions.notifyScreenshotAction(
- intent.getStringExtra(EXTRA_ID), ACTION_TYPE_DELETE, false, null);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
index c4287ca..864f29a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
@@ -126,7 +126,7 @@
/**
* Writes the given Bitmap to outputFile.
*/
- ListenableFuture<File> exportToRawFile(Executor executor, Bitmap bitmap,
+ public ListenableFuture<File> exportToRawFile(Executor executor, Bitmap bitmap,
final File outputFile) {
return CallbackToFutureAdapter.getFuture(
(completer) -> {
@@ -196,7 +196,7 @@
* @param bitmap the bitmap to export
* @return a listenable future result
*/
- ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
+ public ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
ZonedDateTime captureTime, UserHandle owner, int displayId) {
return export(executor, new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat,
mQuality, /* publish */ true, owner, mFlags,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
index 1f6d212..a1481f6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
@@ -34,6 +34,7 @@
import com.android.internal.logging.UiEventLogger
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.res.R
+import com.android.systemui.screenshot.scroll.ScrollCaptureController
import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER
import dagger.assisted.Assisted
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LogConfig.java b/packages/SystemUI/src/com/android/systemui/screenshot/LogConfig.java
index 6050c2b..440cf1c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LogConfig.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LogConfig.java
@@ -16,8 +16,9 @@
package com.android.systemui.screenshot;
+/** Stores debug log configuration for screenshots. */
@SuppressWarnings("PointlessBooleanExpression")
-class LogConfig {
+public class LogConfig {
/** Log ALL the things... */
private static final boolean DEBUG_ALL = false;
@@ -29,36 +30,37 @@
private static final boolean TAG_WITH_CLASS_NAME = false;
/** Action creation and user selection: Share, Save, Edit, Delete, Smart action, etc */
- static final boolean DEBUG_ACTIONS = DEBUG_ALL || false;
+ public static final boolean DEBUG_ACTIONS = DEBUG_ALL || false;
/** Debug info about animations such as start, complete and cancel */
- static final boolean DEBUG_ANIM = DEBUG_ALL || false;
+ public static final boolean DEBUG_ANIM = DEBUG_ALL || false;
/** Whenever Uri is supplied to consumer, or onComplete runnable is run() */
- static final boolean DEBUG_CALLBACK = DEBUG_ALL || false;
+ public static final boolean DEBUG_CALLBACK = DEBUG_ALL || false;
/** Logs information about dismissing the screenshot tool */
- static final boolean DEBUG_DISMISS = DEBUG_ALL || false;
+ public static final boolean DEBUG_DISMISS = DEBUG_ALL || false;
/** Touch or key event driven action or side effects */
- static final boolean DEBUG_INPUT = DEBUG_ALL || false;
+ public static final boolean DEBUG_INPUT = DEBUG_ALL || false;
/** Scroll capture usage */
- static final boolean DEBUG_SCROLL = DEBUG_ALL || false;
+ public static final boolean DEBUG_SCROLL = DEBUG_ALL || false;
/** Service lifecycle events and callbacks */
- static final boolean DEBUG_SERVICE = DEBUG_ALL || false;
+ public static final boolean DEBUG_SERVICE = DEBUG_ALL || false;
/** Storage related actions, Bitmap.compress, ContentManager, etc */
- static final boolean DEBUG_STORAGE = DEBUG_ALL || false;
+ public static final boolean DEBUG_STORAGE = DEBUG_ALL || false;
/** High level logical UI actions: timeout, onConfigChanged, insets, show actions, reset */
- static final boolean DEBUG_UI = DEBUG_ALL || false;
+ public static final boolean DEBUG_UI = DEBUG_ALL || false;
/** Interactions with Window and WindowManager */
- static final boolean DEBUG_WINDOW = DEBUG_ALL || false;
+ public static final boolean DEBUG_WINDOW = DEBUG_ALL || false;
- static String logTag(Class<?> cls) {
+ /** Get the appropriate class name */
+ public static String logTag(Class<?> cls) {
return TAG_WITH_CLASS_NAME ? cls.getSimpleName() : TAG_SS;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 198a29c..c8e13bb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -87,6 +87,10 @@
import com.android.systemui.flags.Flags;
import com.android.systemui.res.R;
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
+import com.android.systemui.screenshot.scroll.LongScreenshotActivity;
+import com.android.systemui.screenshot.scroll.LongScreenshotData;
+import com.android.systemui.screenshot.scroll.ScrollCaptureClient;
+import com.android.systemui.screenshot.scroll.ScrollCaptureController;
import com.android.systemui.util.Assert;
import com.google.common.util.concurrent.ListenableFuture;
@@ -200,7 +204,7 @@
void onActionsReady(ScreenshotController.QuickShareData quickShareData);
}
- interface TransitionDestination {
+ public interface TransitionDestination {
/**
* Allows the long screenshot activity to call back with a destination location (the bounds
* on screen of the destination for the transitioning view) and a Runnable to be run once
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 1c5a8a1..cb2dba0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -92,6 +92,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.res.R;
+import com.android.systemui.screenshot.scroll.ScrollCaptureController;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.shared.system.QuickStepContract;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
index 182b889..6be32a9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
@@ -25,6 +25,7 @@
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
+import com.android.systemui.screenshot.scroll.ScrollCaptureController
/** Abstraction of the surface between ScreenshotController and ScreenshotView */
interface ScreenshotViewProxy {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
index aa23d6b..d87d85b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
@@ -52,7 +52,7 @@
import com.android.internal.logging.UiEventLogger.UiEventEnum;
import com.android.settingslib.Utils;
import com.android.systemui.res.R;
-import com.android.systemui.screenshot.CropView;
+import com.android.systemui.screenshot.scroll.CropView;
import com.android.systemui.settings.UserTracker;
import javax.inject.Inject;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
rename to packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
index 2f411ea..5e561cf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
import android.animation.ValueAnimator;
import android.content.Context;
@@ -265,7 +265,7 @@
Log.w(TAG, "No boundary selected");
break;
}
- Log.i(TAG, "Updated mCrop: " + mCrop);
+ Log.i(TAG, "Updated mCrop: " + mCrop);
invalidate();
}
@@ -385,7 +385,7 @@
/**
* @param action either ACTION_DOWN, ACTION_UP or ACTION_MOVE.
- * @param x coordinate of the relevant pointer.
+ * @param x x-coordinate of the relevant pointer.
*/
private void updateListener(int action, float x) {
if (mCropInteractionListener != null && isVertical(mCurrentDraggingBoundary)) {
@@ -643,11 +643,13 @@
/**
* Listen for crop motion events and state.
*/
- public interface CropInteractionListener {
+ interface CropInteractionListener {
void onCropDragStarted(CropBoundary boundary, float boundaryPosition,
int boundaryPositionPx, float horizontalCenter, float x);
+
void onCropDragMoved(CropBoundary boundary, float boundaryPosition,
int boundaryPositionPx, float horizontalCenter, float x);
+
void onCropDragComplete();
}
@@ -675,8 +677,7 @@
out.writeParcelable(mCrop, 0);
}
- public static final Parcelable.Creator<SavedState> CREATOR
- = new Parcelable.Creator<SavedState>() {
+ public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageLoader.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageLoader.java
similarity index 81%
rename from packages/SystemUI/src/com/android/systemui/screenshot/ImageLoader.java
rename to packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageLoader.java
index 7ee7c31..df86d69 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
import android.annotation.Nullable;
import android.content.ContentResolver;
@@ -35,20 +35,20 @@
import javax.inject.Inject;
/** Loads images. */
-public class ImageLoader {
+class ImageLoader {
private final ContentResolver mResolver;
static class Result {
- @Nullable Uri uri;
- @Nullable File fileName;
- @Nullable Bitmap bitmap;
+ @Nullable Uri mUri;
+ @Nullable File mFilename;
+ @Nullable Bitmap mBitmap;
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Result{");
- sb.append("uri=").append(uri);
- sb.append(", fileName=").append(fileName);
- sb.append(", bitmap=").append(bitmap);
+ sb.append("uri=").append(mUri);
+ sb.append(", fileName=").append(mFilename);
+ sb.append(", bitmap=").append(mBitmap);
sb.append('}');
return sb.toString();
}
@@ -69,11 +69,10 @@
return CallbackToFutureAdapter.getFuture(completer -> {
Result result = new Result();
try (InputStream in = mResolver.openInputStream(uri)) {
- result.uri = uri;
- result.bitmap = BitmapFactory.decodeStream(in);
+ result.mUri = uri;
+ result.mBitmap = BitmapFactory.decodeStream(in);
completer.set(result);
- }
- catch (IOException e) {
+ } catch (IOException e) {
completer.setException(e);
}
return "BitmapFactory#decodeStream";
@@ -91,8 +90,8 @@
return CallbackToFutureAdapter.getFuture(completer -> {
try (InputStream in = new BufferedInputStream(new FileInputStream(file))) {
Result result = new Result();
- result.fileName = file;
- result.bitmap = BitmapFactory.decodeStream(in);
+ result.mFilename = file;
+ result.mBitmap = BitmapFactory.decodeStream(in);
completer.set(result);
} catch (IOException e) {
completer.setException(e);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageTile.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java
rename to packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageTile.java
index a95c91b..c9c297e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageTile.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
import static android.graphics.ColorSpace.Named.SRGB;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageTileSet.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
rename to packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageTileSet.java
index 356f67e..76a72f7 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageTileSet.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
import android.annotation.AnyThread;
import android.graphics.Bitmap;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
rename to packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
index 00d480a..1e1a577 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
import android.app.Activity;
import android.app.ActivityOptions;
@@ -47,11 +47,14 @@
import com.android.internal.view.OneShotPreDrawListener;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.res.R;
-import com.android.systemui.screenshot.CropView.CropBoundary;
-import com.android.systemui.screenshot.ScrollCaptureController.LongScreenshot;
-import com.android.systemui.settings.UserTracker;
+import com.android.systemui.screenshot.ActionIntentCreator;
+import com.android.systemui.screenshot.ActionIntentExecutor;
+import com.android.systemui.screenshot.ImageExporter;
+import com.android.systemui.screenshot.LogConfig;
+import com.android.systemui.screenshot.ScreenshotEvent;
+import com.android.systemui.screenshot.scroll.CropView.CropBoundary;
+import com.android.systemui.screenshot.scroll.ScrollCaptureController.LongScreenshot;
import com.google.common.util.concurrent.ListenableFuture;
@@ -81,8 +84,6 @@
private final ImageExporter mImageExporter;
private final LongScreenshotData mLongScreenshotHolder;
private final ActionIntentExecutor mActionExecutor;
- private final FeatureFlags mFeatureFlags;
- private final UserTracker mUserTracker;
private ImageView mPreview;
private ImageView mTransitionView;
@@ -113,16 +114,13 @@
@Inject
public LongScreenshotActivity(UiEventLogger uiEventLogger, ImageExporter imageExporter,
@Main Executor mainExecutor, @Background Executor bgExecutor,
- LongScreenshotData longScreenshotHolder, ActionIntentExecutor actionExecutor,
- FeatureFlags featureFlags, UserTracker userTracker) {
+ LongScreenshotData longScreenshotHolder, ActionIntentExecutor actionExecutor) {
mUiEventLogger = uiEventLogger;
mUiExecutor = mainExecutor;
mBackgroundExecutor = bgExecutor;
mImageExporter = imageExporter;
mLongScreenshotHolder = longScreenshotHolder;
mActionExecutor = actionExecutor;
- mFeatureFlags = featureFlags;
- mUserTracker = userTracker;
}
@@ -265,13 +263,13 @@
private void onCachedImageLoaded(ImageLoader.Result imageResult) {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_CACHED_IMAGE_LOADED);
- BitmapDrawable drawable = new BitmapDrawable(getResources(), imageResult.bitmap);
+ BitmapDrawable drawable = new BitmapDrawable(getResources(), imageResult.mBitmap);
mPreview.setImageDrawable(drawable);
mPreview.setAlpha(1f);
- mMagnifierView.setDrawable(drawable, imageResult.bitmap.getWidth(),
- imageResult.bitmap.getHeight());
+ mMagnifierView.setDrawable(drawable, imageResult.mBitmap.getWidth(),
+ imageResult.mBitmap.getHeight());
mCropView.setVisibility(View.VISIBLE);
- mSavedImagePath = imageResult.fileName;
+ mSavedImagePath = imageResult.mFilename;
setButtonsEnabled(true);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotData.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotData.java
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotData.java
rename to packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotData.java
index f549faf..ebac5bf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotData.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotData.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.screenshot.ScreenshotController;
import java.util.concurrent.atomic.AtomicReference;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/MagnifierView.java
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
rename to packages/SystemUI/src/com/android/systemui/screenshot/scroll/MagnifierView.java
index 0c543cd..0a1a747 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/MagnifierView.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -64,16 +64,16 @@
private ViewPropertyAnimator mTranslationAnimator;
private final Animator.AnimatorListener mTranslationAnimatorListener =
new AnimatorListenerAdapter() {
- @Override
- public void onAnimationCancel(Animator animation) {
- mTranslationAnimator = null;
- }
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mTranslationAnimator = null;
+ }
- @Override
- public void onAnimationEnd(Animator animation) {
- mTranslationAnimator = null;
- }
- };
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mTranslationAnimator = null;
+ }
+ };
public MagnifierView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureClient.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
rename to packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureClient.java
index e93f737..0e43343 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureClient.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
import static com.android.systemui.screenshot.LogConfig.DEBUG_SCROLL;
@@ -46,6 +46,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.screenshot.LogConfig;
import com.google.common.util.concurrent.ListenableFuture;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureController.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
rename to packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureController.java
index 8a2678c..f4c77da 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
import android.content.Context;
import android.graphics.Bitmap;
@@ -30,8 +30,10 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult;
-import com.android.systemui.screenshot.ScrollCaptureClient.Session;
+import com.android.systemui.screenshot.LogConfig;
+import com.android.systemui.screenshot.ScreenshotEvent;
+import com.android.systemui.screenshot.scroll.ScrollCaptureClient.CaptureResult;
+import com.android.systemui.screenshot.scroll.ScrollCaptureClient.Session;
import com.google.common.util.concurrent.ListenableFuture;
@@ -85,7 +87,7 @@
mImageTileSet = imageTileSet;
}
- /** Returns a bitmap containing the combinded result. */
+ /** Returns a bitmap containing the combined result. */
public Bitmap toBitmap() {
return mImageTileSet.toBitmap();
}
@@ -167,7 +169,7 @@
* {@link ScrollCaptureResponse#isConnected() connected}.
* @return a future ImageTile set containing the result
*/
- ListenableFuture<LongScreenshot> run(ScrollCaptureResponse response) {
+ public ListenableFuture<LongScreenshot> run(ScrollCaptureResponse response) {
mCancelled = false;
return CallbackToFutureAdapter.getFuture(completer -> {
mCaptureCompleter = completer;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/TiledImageDrawable.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java
rename to packages/SystemUI/src/com/android/systemui/screenshot/scroll/TiledImageDrawable.java
index 71df369..00455bc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/TiledImageDrawable.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
import android.annotation.Nullable;
import android.graphics.Canvas;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index de21a73..db06c6b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -31,18 +31,6 @@
* @see NotificationPanelViewController
*/
interface ShadeViewController {
- /**
- * Returns whether the shade height is greater than zero or the shade is expecting a synthesized
- * down event.
- */
- val isPanelExpanded: Boolean
-
- /** Returns whether the shade is in the process of collapsing. */
- val isCollapsing: Boolean
-
- /** Returns whether shade's height is zero. */
- val isFullyCollapsed: Boolean
-
/** Returns whether the shade is tracking touches for expand/collapse of the shade or QS. */
val isTracking: Boolean
@@ -102,19 +90,6 @@
fun showAodUi()
/**
- * This method should not be used anymore, you should probably use [.isShadeFullyOpen] instead.
- * It was overused as indicating if shade is open or we're on keyguard/AOD. Moving forward we
- * should be explicit about the what state we're checking.
- *
- * @return if panel is covering the screen, which means we're in expanded shade or keyguard/AOD
- */
- @Deprecated(
- "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" +
- "{@link #isOnAod()}, {@link #isOnKeyguard()} instead."
- )
- fun isFullyExpanded(): Boolean
-
- /**
* Sends an external (e.g. Status Bar) touch event to the Shade touch handler.
*
* This is different from [startInputFocusTransfer] as it doesn't rely on setting the launcher
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index b67156f..48a2d75 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -33,31 +33,34 @@
ShadeBackActionInteractor,
ShadeLockscreenInteractor,
PanelExpansionInteractor {
- override fun expandToNotifications() {}
- override val isExpanded: Boolean = false
+ @Deprecated("Use ShadeInteractor instead") override fun expandToNotifications() {}
+ @Deprecated("Use ShadeInteractor instead") override val isExpanded: Boolean = false
override val isPanelExpanded: Boolean = false
override fun animateCollapseQs(fullyCollapse: Boolean) {}
override fun canBeCollapsed(): Boolean = false
- override val isCollapsing: Boolean = false
+ @Deprecated("Use ShadeAnimationInteractor instead") override val isCollapsing: Boolean = false
+ @Deprecated("Use !ShadeInteractor.isAnyExpanded instead")
override val isFullyCollapsed: Boolean = false
override val isTracking: Boolean = false
override val isViewEnabled: Boolean = false
override fun shouldHideStatusBarIconsWhenExpanded() = false
- override fun blockExpansionForCurrentTouch() {}
+ @Deprecated("Not supported by scenes") override fun blockExpansionForCurrentTouch() {}
override fun disableHeader(state1: Int, state2: Int, animated: Boolean) {}
override fun startExpandLatencyTracking() {}
override fun startBouncerPreHideAnimation() {}
override fun dozeTimeTick() {}
override fun resetViews(animate: Boolean) {}
override val barState: Int = 0
+ @Deprecated("Only supported by very old devices that will not adopt scenes.")
override fun closeUserSwitcherIfOpen(): Boolean {
return false
}
override fun onBackPressed() {}
+ @Deprecated("According to b/318376223, shade predictive back is not be supported.")
override fun onBackProgressed(progressFraction: Float) {}
override fun setAlpha(alpha: Int, animate: Boolean) {}
override fun setAlphaChangeAnimationEndAction(r: Runnable) {}
- override fun setPulsing(pulsing: Boolean) {}
+ @Deprecated("Not supported by scenes") override fun setPulsing(pulsing: Boolean) {}
override fun setQsScrimEnabled(qsScrimEnabled: Boolean) {}
override fun setAmbientIndicationTop(ambientIndicationTop: Int, ambientTextVisible: Boolean) {}
override fun updateSystemUiStateFlags() {}
@@ -66,14 +69,18 @@
override fun removeOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) {}
override fun transitionToExpandedShade(delay: Long) {}
- override fun resetViewGroupFade() {}
+ @Deprecated("Not supported by scenes") override fun resetViewGroupFade() {}
+ @Deprecated("Not supported by scenes")
override fun setKeyguardTransitionProgress(keyguardAlpha: Float, keyguardTranslationY: Int) {}
- override fun setOverStretchAmount(amount: Float) {}
+ @Deprecated("Not supported by scenes") override fun setOverStretchAmount(amount: Float) {}
+ @Deprecated("TODO(b/325072511) delete this")
override fun setKeyguardStatusBarAlpha(alpha: Float) {}
override fun showAodUi() {}
- override fun isFullyExpanded(): Boolean {
- return false
- }
+ @Deprecated(
+ "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" +
+ "{@link #isOnAod()}, {@link #isOnKeyguard()} instead."
+ )
+ override val isFullyExpanded = false
override fun handleExternalTouch(event: MotionEvent): Boolean {
return false
}
@@ -84,6 +91,7 @@
override val shadeHeadsUpTracker = ShadeHeadsUpTrackerEmptyImpl()
override val shadeFoldAnimator = ShadeFoldAnimatorEmptyImpl()
+ @Deprecated("Use SceneInteractor.currentScene instead.")
override val legacyPanelExpansion = flowOf(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
index 01118bd..60cb061 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
@@ -41,4 +41,29 @@
* backwards-compatibility and should not be consumed by newer code.
*/
@Deprecated("Use SceneInteractor.currentScene instead.") val legacyPanelExpansion: Flow<Float>
+
+ /**
+ * Returns whether the shade height is greater than zero or the shade is expecting a synthesized
+ * down event.
+ */
+ @Deprecated("Use ShadeInteractor.isAnyExpanded instead.") val isPanelExpanded: Boolean
+
+ /**
+ * This method should not be used anymore, you should probably use [.isShadeFullyOpen] instead.
+ * It was overused as indicating if shade is open or we're on keyguard/AOD. Moving forward we
+ * should be explicit about the what state we're checking.
+ *
+ * @return if panel is covering the screen, which means we're in expanded shade or keyguard/AOD
+ */
+ @Deprecated(
+ "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" +
+ "{@link #isOnAod()}, {@link #isOnKeyguard()} instead."
+ )
+ val isFullyExpanded: Boolean
+
+ /** Returns whether shade's height is zero. */
+ @Deprecated("Use !ShadeInteractor.isAnyExpanded instead") val isFullyCollapsed: Boolean
+
+ /** Returns whether the shade is in the process of collapsing. */
+ @Deprecated("Use ShadeAnimationInteractor instead") val isCollapsing: Boolean
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
index 20f73b0..3877677 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
@@ -35,6 +35,8 @@
@Inject
constructor(
sceneInteractor: SceneInteractor,
+ shadeInteractor: ShadeInteractor,
+ shadeAnimationInteractor: ShadeAnimationInteractor,
) : PanelExpansionInteractor {
/**
@@ -93,7 +95,22 @@
}
}
+ @Deprecated(
+ "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" +
+ "{@link #isOnAod()}, {@link #isOnKeyguard()} instead."
+ )
+ override val isFullyExpanded = shadeInteractor.isAnyFullyExpanded.value
+
+ @Deprecated("Use !ShadeInteractor.isAnyExpanded instead")
+ override val isFullyCollapsed = !shadeInteractor.isAnyExpanded.value
+
+ @Deprecated("Use ShadeAnimationInteractor instead")
+ override val isCollapsing =
+ shadeAnimationInteractor.isAnyCloseAnimationRunning.value ||
+ shadeAnimationInteractor.isLaunchingActivity.value
private fun SceneKey.isExpandable(): Boolean {
return this == Scenes.Shade || this == Scenes.QuickSettings
}
+
+ override val isPanelExpanded = shadeInteractor.isAnyExpanded.value
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt
index 5a777e8..134c983 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt
@@ -17,7 +17,6 @@
package com.android.systemui.shade.domain.interactor
import com.android.systemui.shade.data.repository.ShadeAnimationRepository
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -38,5 +37,5 @@
* completes the close. Important: if QS is collapsing back to shade, this will be false because
* that is not considered "closing".
*/
- abstract val isAnyCloseAnimationRunning: Flow<Boolean>
+ abstract val isAnyCloseAnimationRunning: StateFlow<Boolean>
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt
index 2a7658a..f364d6d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt
@@ -19,7 +19,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.shade.data.repository.ShadeAnimationRepository
import javax.inject.Inject
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.MutableStateFlow
/** Implementation of ShadeAnimationInteractor for shadeless SysUI variants. */
@SysUISingleton
@@ -28,5 +28,5 @@
constructor(
shadeAnimationRepository: ShadeAnimationRepository,
) : ShadeAnimationInteractor(shadeAnimationRepository) {
- override val isAnyCloseAnimationRunning = flowOf(false)
+ override val isAnyCloseAnimationRunning = MutableStateFlow(false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
index eaac8ae..d9982e3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
@@ -18,21 +18,26 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.ShadeAnimationRepository
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
/** Implementation of ShadeAnimationInteractor compatible with the scene container framework. */
@SysUISingleton
class ShadeAnimationInteractorSceneContainerImpl
@Inject
constructor(
+ @Background scope: CoroutineScope,
shadeAnimationRepository: ShadeAnimationRepository,
sceneInteractor: SceneInteractor,
) : ShadeAnimationInteractor(shadeAnimationRepository) {
@@ -56,4 +61,5 @@
}
}
.distinctUntilChanged()
+ .stateIn(scope, SharingStarted.Eagerly, false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
index 6414af3..421a761 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
@@ -46,7 +46,10 @@
sharedNotificationContainerInteractor: SharedNotificationContainerInteractor,
repository: ShadeRepository,
) : BaseShadeInteractor {
- /** The amount [0-1] that the shade has been opened */
+ /**
+ * The amount [0-1] that the shade has been opened. Uses stateIn to avoid redundant calculations
+ * in downstream flows.
+ */
override val shadeExpansion: Flow<Float> =
combine(
repository.lockscreenShadeExpansion,
@@ -71,6 +74,7 @@
}
}
.distinctUntilChanged()
+ .stateIn(scope, SharingStarted.Eagerly, 0f)
override val qsExpansion: StateFlow<Float> = repository.qsExpansion
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 4275fc6..4406813 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -178,6 +178,7 @@
private static final int MSG_IMMERSIVE_CHANGED = 78 << MSG_SHIFT;
private static final int MSG_SET_QS_TILES = 79 << MSG_SHIFT;
private static final int MSG_ENTER_DESKTOP = 80 << MSG_SHIFT;
+ private static final int MSG_SET_SPLITSCREEN_FOCUS = 81 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
public static final int FLAG_EXCLUDE_RECENTS_PANEL = 1 << 1;
@@ -508,6 +509,11 @@
default void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) {}
/**
+ * @see IStatusBar#setSplitscreenFocus
+ */
+ default void setSplitscreenFocus(boolean leftOrTop) {}
+
+ /**
* @see IStatusBar#showMediaOutputSwitcher
*/
default void showMediaOutputSwitcher(String packageName) {}
@@ -1349,6 +1355,12 @@
}
@Override
+ public void setSplitscreenFocus(boolean leftOrTop) {
+ synchronized (mLock) {
+ mHandler.obtainMessage(MSG_SET_SPLITSCREEN_FOCUS, leftOrTop).sendToTarget();
+ }
+ }
+ @Override
public void showMediaOutputSwitcher(String packageName) {
int callingUid = Binder.getCallingUid();
if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
@@ -1919,6 +1931,11 @@
}
break;
}
+ case MSG_SET_SPLITSCREEN_FOCUS:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).setSplitscreenFocus((Boolean) msg.obj);
+ }
+ break;
case MSG_SHOW_MEDIA_OUTPUT_SWITCHER:
args = (SomeArgs) msg.obj;
String clientPackageName = (String) args.arg1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt
index 1bebbfd..ca73081 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt
@@ -18,12 +18,16 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
@@ -46,6 +50,8 @@
keyguardTransitionInteractor: KeyguardTransitionInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
powerInteractor: PowerInteractor,
+ wmLockscreenVisibilityInteractor: WindowManagerLockscreenVisibilityInteractor,
+ surfaceBehindInteractor: KeyguardSurfaceBehindInteractor,
) {
/** Occlusion state to apply whenever a keyguard transition is STARTED, if any. */
private val occlusionStateFromStartedStep: Flow<OccludedState> =
@@ -98,11 +104,21 @@
OccludedState(occluded = occluded, animate = false)
}
- /** Occlusion state to apply to SKBVM's setOccluded call. */
+ /** Occlusion state to apply to SBKVM's setOccluded call. */
val keyguardViewOcclusionState =
merge(occlusionStateFromStartedStep, occlusionStateFromFinishedStep)
.distinctUntilChangedBy {
// Don't switch 'animate' values mid-transition.
it.occluded
}
+
+ /** Visibility state to apply to SBKVM via show() and hide(). */
+ val keyguardViewVisibility =
+ combine(
+ wmLockscreenVisibilityInteractor.lockscreenVisibility,
+ surfaceBehindInteractor.isAnimatingSurface,
+ ) { lockscreenVisible, animatingSurface ->
+ lockscreenVisible || animatingSurface
+ }
+ .distinctUntilChanged()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
index 2707ed8..b77748e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
@@ -28,6 +28,7 @@
import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_KEYGUARD_OCCLUDED
import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_KEYGUARD_SHOWING
import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_LOCKED_SHADE
+import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_USER_SETUP_INCOMPLETE
import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_EXPECTED_TO_HUN
import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_NOT_IMPORTANT_ENOUGH
import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_NO_FULL_SCREEN_INTENT
@@ -101,6 +102,7 @@
FSI_KEYGUARD_OCCLUDED(true, "keyguard is occluded"),
FSI_LOCKED_SHADE(true, "locked shade"),
FSI_DEVICE_NOT_PROVISIONED(true, "device not provisioned"),
+ FSI_USER_SETUP_INCOMPLETE(true, "user setup incomplete"),
NO_FSI_NO_HUN_OR_KEYGUARD(
false,
"no HUN or keyguard",
@@ -189,6 +191,10 @@
return FSI_DEVICE_NOT_PROVISIONED
}
+ if (!deviceProvisionedController.isCurrentUserSetup) {
+ return FSI_USER_SETUP_INCOMPLETE
+ }
+
return NO_FSI_NO_HUN_OR_KEYGUARD
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
index b0155f1..c084482 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
@@ -104,7 +104,11 @@
/**
* The device is not provisioned, launch FSI.
*/
- FSI_NOT_PROVISIONED(true);
+ FSI_NOT_PROVISIONED(true),
+ /**
+ * The current user has not completed setup, launch FSI.
+ */
+ FSI_USER_SETUP_INCOMPLETE(true);
public final boolean shouldLaunch;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index dc9eeb3..a655c72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -362,6 +362,12 @@
suppressedByDND);
}
+ // The current user hasn't completed setup, launch FSI.
+ if (!mDeviceProvisionedController.isCurrentUserSetup()) {
+ return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_USER_SETUP_INCOMPLETE,
+ suppressedByDND);
+ }
+
// Detect the case determined by b/231322873 to launch FSI while device is in use,
// as blocked by the correct implementation, and report the event.
return getDecisionGivenSuppression(FullScreenIntentDecision.NO_FSI_NO_HUN_OR_KEYGUARD,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 77e9425..9479762 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -49,6 +49,7 @@
import android.graphics.Path;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.SystemClock;
import android.os.Trace;
import android.provider.Settings;
import android.util.AttributeSet;
@@ -208,6 +209,9 @@
private float mQsExpansionFraction;
private final int mSplitShadeMinContentHeight;
private String mLastUpdateSidePaddingDumpString;
+ private long mLastUpdateSidePaddingElapsedRealtime;
+ private String mLastInitViewDumpString;
+ private long mLastInitViewElapsedRealtime;
/**
* The algorithm which calculates the properties for our children
@@ -887,17 +891,34 @@
mOverflingDistance = configuration.getScaledOverflingDistance();
Resources res = context.getResources();
+ final boolean isSmallScreenLandscape = res.getBoolean(R.bool.is_small_screen_landscape);
boolean useSmallLandscapeLockscreenResources = mIsSmallLandscapeLockscreenEnabled
- && res.getBoolean(R.bool.is_small_screen_landscape);
+ && isSmallScreenLandscape;
// TODO (b/293252410) remove condition here when flag is launched
// Instead update the config_skinnyNotifsInLandscape to be false whenever
// is_small_screen_landscape is true. Then, only use the config_skinnyNotifsInLandscape.
+ final boolean configSkinnyNotifsInLandscape = res.getBoolean(
+ R.bool.config_skinnyNotifsInLandscape);
if (useSmallLandscapeLockscreenResources) {
mSkinnyNotifsInLandscape = false;
} else {
- mSkinnyNotifsInLandscape = res.getBoolean(
- R.bool.config_skinnyNotifsInLandscape);
+ mSkinnyNotifsInLandscape = configSkinnyNotifsInLandscape;
}
+
+ mLastInitViewDumpString =
+ "mIsSmallLandscapeLockscreenEnabled=" + mIsSmallLandscapeLockscreenEnabled
+ + " isSmallScreenLandscape=" + isSmallScreenLandscape
+ + " useSmallLandscapeLockscreenResources="
+ + useSmallLandscapeLockscreenResources
+ + " skinnyNotifsInLandscape=" + configSkinnyNotifsInLandscape
+ + " mSkinnyNotifsInLandscape=" + mSkinnyNotifsInLandscape;
+ mLastInitViewElapsedRealtime = SystemClock.elapsedRealtime();
+
+ if (DEBUG_UPDATE_SIDE_PADDING) {
+ Log.v(TAG, "initView @ elapsedRealtime " + mLastInitViewElapsedRealtime + ": "
+ + mLastInitViewDumpString);
+ }
+
mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
mStackScrollAlgorithm.initView(context);
mStateAnimator.initView(context);
@@ -925,22 +946,33 @@
mLastUpdateSidePaddingDumpString = "viewWidth=" + viewWidth
+ " skinnyNotifsInLandscape=" + mSkinnyNotifsInLandscape
+ " orientation=" + orientation;
+ mLastUpdateSidePaddingElapsedRealtime = SystemClock.elapsedRealtime();
if (DEBUG_UPDATE_SIDE_PADDING) {
- Log.v(TAG, "updateSidePadding: " + mLastUpdateSidePaddingDumpString);
+ Log.v(TAG,
+ "updateSidePadding @ elapsedRealtime " + mLastUpdateSidePaddingElapsedRealtime
+ + ": " + mLastUpdateSidePaddingDumpString);
}
- if (viewWidth == 0 || !mSkinnyNotifsInLandscape) {
+ if (viewWidth == 0) {
+ Log.e(TAG, "updateSidePadding: viewWidth is zero");
mSidePaddings = mMinimumPaddings;
return;
}
- // Portrait is easy, just use the dimen for paddings
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
mSidePaddings = mMinimumPaddings;
return;
}
+ if (mShouldUseSplitNotificationShade) {
+ if (mSkinnyNotifsInLandscape) {
+ Log.e(TAG, "updateSidePadding: mSkinnyNotifsInLandscape has betrayed us!");
+ }
+ mSidePaddings = mMinimumPaddings;
+ return;
+ }
+
final int innerWidth = viewWidth - mMinimumPaddings * 2;
final int qsTileWidth = (innerWidth - mQsTilePadding * 3) / 4;
mSidePaddings = mMinimumPaddings + qsTileWidth + mQsTilePadding;
@@ -4884,6 +4916,7 @@
public void dump(PrintWriter pwOriginal, String[] args) {
IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
pw.println("Internal state:");
DumpUtilsKt.withIncreasedIndent(pw, () -> {
println(pw, "pulsing", mPulsing);
@@ -4914,7 +4947,17 @@
println(pw, "minimumPaddings", mMinimumPaddings);
println(pw, "qsTilePadding", mQsTilePadding);
println(pw, "sidePaddings", mSidePaddings);
+ println(pw, "elapsedRealtime", elapsedRealtime);
+ println(pw, "lastInitView", mLastInitViewDumpString);
+ println(pw, "lastInitViewElapsedRealtime", mLastInitViewElapsedRealtime);
+ println(pw, "lastInitViewMillisAgo", elapsedRealtime - mLastInitViewElapsedRealtime);
+ println(pw, "shouldUseSplitNotificationShade", mShouldUseSplitNotificationShade);
println(pw, "lastUpdateSidePadding", mLastUpdateSidePaddingDumpString);
+ println(pw, "lastUpdateSidePaddingElapsedRealtime",
+ mLastUpdateSidePaddingElapsedRealtime);
+ println(pw, "lastUpdateSidePaddingMillisAgo",
+ elapsedRealtime - mLastUpdateSidePaddingElapsedRealtime);
+ println(pw, "isSmallLandscapeLockscreenEnabled", mIsSmallLandscapeLockscreenEnabled);
mNotificationStackSizeCalculator.dump(pw, args);
});
pw.println();
@@ -5482,6 +5525,7 @@
mAmbientState.setUseSplitShade(split);
updateDismissBehavior();
updateUseRoundedRectClipping();
+ requestLayout();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index ece7a7f..5b8b91c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.notification.stack.ui.viewbinder
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
import android.view.View
import android.view.WindowInsets
import androidx.lifecycle.Lifecycle
@@ -118,13 +116,6 @@
"SharedNotificationContainerVB (collapseFadeIn)"
)
}
- addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- viewModel.setShadeCollapseFadeInComplete(true)
- }
- }
- )
start()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 2745817..2c4813a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -67,7 +67,6 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@@ -79,6 +78,7 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformWhile
import kotlinx.coroutines.isActive
@@ -124,6 +124,23 @@
setOf(AOD, LOCKSCREEN, DOZING, ALTERNATE_BOUNCER, PRIMARY_BOUNCER)
/**
+ * Is either shade/qs expanded? This intentionally does not use the [ShadeInteractor] version,
+ * as the legacy implementation has extra logic that produces incorrect results.
+ */
+ private val isAnyExpanded =
+ combine(
+ shadeInteractor.shadeExpansion.map { it > 0f },
+ shadeInteractor.qsExpansion.map { it > 0f },
+ ) { shadeExpansion, qsExpansion ->
+ shadeExpansion || qsExpansion
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ )
+
+ /**
* Shade locked is a legacy concept, but necessary to mimic current functionality. Listen for
* both SHADE_LOCKED and shade/qs expansion in order to determine lock state, as one can arrive
* before the other.
@@ -131,17 +148,17 @@
private val isShadeLocked: Flow<Boolean> =
combine(
keyguardInteractor.statusBarState.map { it == SHADE_LOCKED },
- shadeInteractor.qsExpansion.map { it > 0f },
- shadeInteractor.shadeExpansion.map { it > 0f },
- ) { isShadeLocked, isQsExpanded, isShadeExpanded ->
- isShadeLocked && (isQsExpanded || isShadeExpanded)
+ isAnyExpanded,
+ ) { isShadeLocked, isAnyExpanded ->
+ isShadeLocked && isAnyExpanded
}
- .distinctUntilChanged()
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ )
.dumpWhileCollecting("isShadeLocked")
- private val shadeCollapseFadeInComplete =
- MutableStateFlow(false).dumpValue("shadeCollapseFadeInComplete")
-
val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
interactor.configurationBasedDimensions
.map {
@@ -176,20 +193,16 @@
) { constrainedNotificationState, transitioningToOrFromLockscreen ->
constrainedNotificationState || transitioningToOrFromLockscreen
}
- .distinctUntilChanged()
+ .shareIn(scope = applicationScope, started = SharingStarted.Eagerly)
.dumpWhileCollecting("isOnLockscreen")
/** Are we purely on the keyguard without the shade/qs? */
val isOnLockscreenWithoutShade: Flow<Boolean> =
combine(
isOnLockscreen,
- // Shade with notifications
- shadeInteractor.shadeExpansion.map { it > 0f },
- // Shade without notifications, quick settings only (pull down from very top on
- // lockscreen)
- shadeInteractor.qsExpansion.map { it > 0f },
- ) { isKeyguard, isShadeVisible, qsExpansion ->
- isKeyguard && !(isShadeVisible || qsExpansion)
+ isAnyExpanded,
+ ) { isKeyguard, isAnyExpanded ->
+ isKeyguard && !isAnyExpanded
}
.stateIn(
scope = applicationScope,
@@ -219,13 +232,9 @@
val isOnGlanceableHubWithoutShade: Flow<Boolean> =
combine(
isOnGlanceableHub,
- // Shade with notifications
- shadeInteractor.shadeExpansion.map { it > 0f },
- // Shade without notifications, quick settings only (pull down from very top on
- // lockscreen)
- shadeInteractor.qsExpansion.map { it > 0f },
- ) { isGlanceableHub, isShadeVisible, qsExpansion ->
- isGlanceableHub && !(isShadeVisible || qsExpansion)
+ isAnyExpanded,
+ ) { isGlanceableHub, isAnyExpanded ->
+ isGlanceableHub && !isAnyExpanded
}
.stateIn(
scope = applicationScope,
@@ -283,9 +292,6 @@
awaitCollapse().collect { doFadeIn ->
if (doFadeIn) {
emit(true)
- // ... and then for the animation to complete
- shadeCollapseFadeInComplete.first { it }
- shadeCollapseFadeInComplete.value = false
}
}
}
@@ -295,7 +301,7 @@
started = SharingStarted.WhileSubscribed(),
initialValue = false,
)
- .dumpWhileCollecting("shadeCollapseFadeIn")
+ .dumpValue("shadeCollapseFadeIn")
/**
* The container occupies the entire screen, and must be positioned relative to other elements.
@@ -323,7 +329,7 @@
// When QS expansion > 0, it should directly set the top padding so do not
// animate it
val animate = qsExpansion == 0f && !isInTransitionToAnyState
- keyguardInteractor.notificationContainerBounds.value.copy(
+ bounds.copy(
top = top,
isAnimated = animate,
)
@@ -350,6 +356,9 @@
if (shadeExpansion > 0f || qsExpansion > 0f) {
if (configurationBasedDimensions.useSplitShade) {
emit(1f)
+ } else if (qsExpansion == 1f) {
+ // Ensure HUNs will be visible in QS shade (at least while unlocked)
+ emit(1f)
} else {
// Fade as QS shade expands
emit(1f - qsExpansion)
@@ -544,10 +553,6 @@
interactor.notificationStackChanged()
}
- fun setShadeCollapseFadeInComplete(complete: Boolean) {
- shadeCollapseFadeInComplete.value = complete
- }
-
data class ConfigurationBasedDimensions(
val marginStart: Int,
val marginTop: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index ab9ecab..1906b7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -57,8 +57,8 @@
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -81,6 +81,7 @@
private final com.android.systemui.shade.ShadeController mShadeController;
private final CommandQueue mCommandQueue;
private final ShadeViewController mShadeViewController;
+ private final PanelExpansionInteractor mPanelExpansionInteractor;
private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
private final MetricsLogger mMetricsLogger;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -95,7 +96,6 @@
private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
private final PowerManager mPowerManager;
private final Optional<Vibrator> mVibratorOptional;
- private final DisableFlagsLogger mDisableFlagsLogger;
private final int mDisplayId;
private final UserTracker mUserTracker;
private final boolean mVibrateOnOpening;
@@ -120,6 +120,7 @@
ShadeController shadeController,
CommandQueue commandQueue,
ShadeViewController shadeViewController,
+ PanelExpansionInteractor panelExpansionInteractor,
RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
MetricsLogger metricsLogger,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -134,7 +135,6 @@
StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
PowerManager powerManager,
Optional<Vibrator> vibratorOptional,
- DisableFlagsLogger disableFlagsLogger,
@DisplayId int displayId,
Lazy<CameraLauncher> cameraLauncherLazy,
UserTracker userTracker,
@@ -147,6 +147,7 @@
mShadeController = shadeController;
mCommandQueue = commandQueue;
mShadeViewController = shadeViewController;
+ mPanelExpansionInteractor = panelExpansionInteractor;
mRemoteInputQuickSettingsDisabler = remoteInputQuickSettingsDisabler;
mMetricsLogger = metricsLogger;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -161,7 +162,6 @@
mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager;
mPowerManager = powerManager;
mVibratorOptional = vibratorOptional;
- mDisableFlagsLogger = disableFlagsLogger;
mDisplayId = displayId;
mCameraLauncherLazy = cameraLauncherLazy;
mUserTracker = userTracker;
@@ -304,7 +304,7 @@
mShadeController.animateCollapseShade();
} else if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN == key.getKeyCode()) {
mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_DOWN);
- if (mShadeViewController.isFullyCollapsed()) {
+ if (mPanelExpansionInteractor.isFullyCollapsed()) {
if (mVibrateOnOpening) {
vibrateOnNavigationKeyDown();
}
@@ -371,7 +371,7 @@
mStatusBarKeyguardViewManager.reset(true /* hide */);
}
mCameraLauncherLazy.get().launchCamera(source,
- mShadeViewController.isFullyCollapsed());
+ mPanelExpansionInteractor.isFullyCollapsed());
mCentralSurfaces.updateScrimController();
} else {
// We need to defer the camera launch until the screen comes on, since otherwise
@@ -485,7 +485,7 @@
@Override
public void togglePanel() {
- if (mShadeViewController.isPanelExpanded()) {
+ if (mPanelExpansionInteractor.isPanelExpanded()) {
mShadeController.animateCollapseShade();
} else {
mShadeController.animateExpandShade();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FoldStateListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FoldStateListener.kt
index 56b0d598..db237e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FoldStateListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FoldStateListener.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone
import android.content.Context
+import android.hardware.devicestate.DeviceState
import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback
import com.android.internal.R
@@ -45,13 +46,13 @@
private var wasFolded: Boolean? = null
- override fun onStateChanged(state: Int) {
- val isFolded = foldedDeviceStates.contains(state)
+ override fun onDeviceStateChanged(state: DeviceState) {
+ val isFolded = foldedDeviceStates.contains(state.identifier)
if (wasFolded == isFolded) {
return
}
wasFolded = isFolded
- val willGoToSleep = goToSleepDeviceStates.contains(state)
+ val willGoToSleep = goToSleepDeviceStates.contains(state.identifier)
listener.onFoldStateChanged(isFolded, willGoToSleep)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index a155e94..24be3db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -43,6 +43,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.AnimationStateHandler;
+import com.android.systemui.statusbar.policy.AvalancheController;
import com.android.systemui.statusbar.policy.BaseHeadsUpManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
@@ -124,9 +125,10 @@
AccessibilityManagerWrapper accessibilityManagerWrapper,
UiEventLogger uiEventLogger,
JavaAdapter javaAdapter,
- ShadeInteractor shadeInteractor) {
+ ShadeInteractor shadeInteractor,
+ AvalancheController avalancheController) {
super(context, logger, handler, globalSettings, systemClock, executor,
- accessibilityManagerWrapper, uiEventLogger);
+ accessibilityManagerWrapper, uiEventLogger, avalancheController);
Resources resources = mContext.getResources();
mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
statusBarStateController.addCallback(mStatusBarStateListener);
@@ -279,7 +281,7 @@
if (headsUpEntry != null && headsUpEntry.mRemoteInputActive != remoteInputActive) {
headsUpEntry.mRemoteInputActive = remoteInputActive;
if (remoteInputActive) {
- headsUpEntry.removeAutoRemovalCallbacks("setRemoteInputActive(true)");
+ headsUpEntry.cancelAutoRemovalCallbacks("setRemoteInputActive(true)");
} else {
headsUpEntry.updateEntry(false /* updatePostTime */, "setRemoteInputActive(false)");
}
@@ -482,7 +484,7 @@
this.mExpanded = expanded;
if (expanded) {
- removeAutoRemovalCallbacks("setExpanded(true)");
+ cancelAutoRemovalCallbacks("setExpanded(true)");
} else {
updateEntry(false /* updatePostTime */, "setExpanded(false)");
}
@@ -495,7 +497,7 @@
mGutsShownPinned = gutsShownPinned;
if (gutsShownPinned) {
- removeAutoRemovalCallbacks("setGutsShownPinned(true)");
+ cancelAutoRemovalCallbacks("setGutsShownPinned(true)");
} else {
updateEntry(false /* updatePostTime */, "setGutsShownPinned(false)");
}
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 f29ec8f3..92fd90a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -32,6 +32,7 @@
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.window.StatusBarWindowStateController
@@ -58,6 +59,7 @@
private val statusBarWindowStateController: StatusBarWindowStateController,
private val shadeController: ShadeController,
private val shadeViewController: ShadeViewController,
+ private val panelExpansionInteractor: PanelExpansionInteractor,
private val windowRootView: Provider<WindowRootView>,
private val shadeLogger: ShadeLogger,
private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
@@ -218,7 +220,7 @@
)
return true
}
- if (shadeViewController.isFullyCollapsed && event.y < 1f) {
+ if (panelExpansionInteractor.isFullyCollapsed && event.y < 1f) {
// b/235889526 Eat events on the top edge of the phone when collapsed
shadeLogger.logMotionEvent(event, "top edge touch ignored")
return true
@@ -271,6 +273,7 @@
private val statusBarWindowStateController: StatusBarWindowStateController,
private val shadeController: ShadeController,
private val shadeViewController: ShadeViewController,
+ private val panelExpansionInteractor: PanelExpansionInteractor,
private val windowRootView: Provider<WindowRootView>,
private val shadeLogger: ShadeLogger,
private val viewUtil: ViewUtil,
@@ -292,6 +295,7 @@
statusBarWindowStateController,
shadeController,
shadeViewController,
+ panelExpansionInteractor,
windowRootView,
shadeLogger,
statusBarMoveFromCenterAnimationController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index 235ed25..69dd507 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -20,6 +20,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
@@ -38,6 +39,7 @@
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final StatusBarWindowController mStatusBarWindowController;
private final ShadeViewController mShadeViewController;
+ private final PanelExpansionInteractor mPanelExpansionInteractor;
private final NotificationStackScrollLayoutController mNsslController;
private final KeyguardBypassController mKeyguardBypassController;
private final HeadsUpManager mHeadsUpManager;
@@ -49,6 +51,7 @@
NotificationShadeWindowController notificationShadeWindowController,
StatusBarWindowController statusBarWindowController,
ShadeViewController shadeViewController,
+ PanelExpansionInteractor panelExpansionInteractor,
NotificationStackScrollLayoutController nsslController,
KeyguardBypassController keyguardBypassController,
HeadsUpManager headsUpManager,
@@ -57,6 +60,7 @@
mNotificationShadeWindowController = notificationShadeWindowController;
mStatusBarWindowController = statusBarWindowController;
mShadeViewController = shadeViewController;
+ mPanelExpansionInteractor = panelExpansionInteractor;
mNsslController = nsslController;
mKeyguardBypassController = keyguardBypassController;
mHeadsUpManager = headsUpManager;
@@ -74,13 +78,13 @@
if (inPinnedMode) {
mNotificationShadeWindowController.setHeadsUpShowing(true);
mStatusBarWindowController.setForceStatusBarVisible(true);
- if (mShadeViewController.isFullyCollapsed()) {
+ if (mPanelExpansionInteractor.isFullyCollapsed()) {
mShadeViewController.updateTouchableRegion();
}
} else {
boolean bypassKeyguard = mKeyguardBypassController.getBypassEnabled()
&& mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
- if (!mShadeViewController.isFullyCollapsed()
+ if (!mPanelExpansionInteractor.isFullyCollapsed()
|| mShadeViewController.isTracking()
|| bypassKeyguard) {
// We are currently tracking or is open and the shade doesn't need to
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 7dd328a..f99817a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -23,7 +23,6 @@
import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
-import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -76,6 +75,8 @@
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
import com.android.systemui.keyguard.shared.model.DismissAction;
import com.android.systemui.keyguard.shared.model.KeyguardDone;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.TaskbarDelegate;
@@ -114,8 +115,10 @@
import javax.inject.Inject;
+import kotlin.Unit;
import kotlinx.coroutines.CoroutineDispatcher;
import kotlinx.coroutines.ExperimentalCoroutinesApi;
+import kotlinx.coroutines.Job;
/**
* Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
@@ -163,6 +166,9 @@
private final Lazy<ShadeController> mShadeController;
private final Lazy<SceneInteractor> mSceneInteractorLazy;
+ private Job mListenForAlternateBouncerTransitionSteps = null;
+ private Job mListenForKeyguardAuthenticatedBiometricsHandled = null;
+
// Local cache of expansion events, to avoid duplicates
private float mFraction = -1f;
private boolean mTracking = false;
@@ -491,20 +497,31 @@
mDockManager.addListener(mDockEventListener);
mIsDocked = mDockManager.isDocked();
}
+ if (mListenForAlternateBouncerTransitionSteps != null) {
+ mListenForAlternateBouncerTransitionSteps.cancel(null);
+ }
+ mListenForAlternateBouncerTransitionSteps = null;
+ if (mListenForKeyguardAuthenticatedBiometricsHandled != null) {
+ mListenForKeyguardAuthenticatedBiometricsHandled.cancel(null);
+ }
+ mListenForKeyguardAuthenticatedBiometricsHandled = null;
+ if (!DeviceEntryUdfpsRefactor.isEnabled()) {
+ mListenForAlternateBouncerTransitionSteps = mJavaAdapter.alwaysCollectFlow(
+ mKeyguardTransitionInteractor.transitionStepsFromState(
+ KeyguardState.ALTERNATE_BOUNCER),
+ this::consumeFromAlternateBouncerTransitionSteps
+ );
+
+ mListenForKeyguardAuthenticatedBiometricsHandled = mJavaAdapter.alwaysCollectFlow(
+ mPrimaryBouncerInteractor.getKeyguardAuthenticatedBiometricsHandled(),
+ this::consumeKeyguardAuthenticatedBiometricsHandled
+ );
+ }
if (KeyguardWmStateRefactor.isEnabled()) {
// Show the keyguard views whenever we've told WM that the lockscreen is visible.
mJavaAdapter.alwaysCollectFlow(
- combineFlows(
- mWmLockscreenVisibilityInteractor.get().getLockscreenVisibility(),
- mSurfaceBehindInteractor.get().isAnimatingSurface(),
- (lockscreenVis, animatingSurface) ->
- // TODO(b/322546110): Waiting until we're not animating the
- // surface is a workaround to avoid jank. We should actually
- // fix the source of the jank, and then hide the keyguard
- // view without waiting for the animation to end.
- lockscreenVis || animatingSurface
- ),
+ mStatusBarKeyguardViewManagerInteractor.getKeyguardViewVisibility(),
this::consumeShowStatusBarKeyguardView);
mJavaAdapter.alwaysCollectFlow(
@@ -514,6 +531,22 @@
}
}
+ @VisibleForTesting
+ void consumeFromAlternateBouncerTransitionSteps(TransitionStep step) {
+ hideAlternateBouncer(false);
+ }
+
+ /**
+ * Required without fix for b/328643370: missing AlternateBouncer (when occluded) => Gone
+ * transition.
+ */
+ @VisibleForTesting
+ void consumeKeyguardAuthenticatedBiometricsHandled(Unit handled) {
+ if (mAlternateBouncerInteractor.isVisibleState()) {
+ hideAlternateBouncer(false);
+ }
+ }
+
private void consumeShowStatusBarKeyguardView(boolean show) {
if (show != mLastShowing) {
if (show) {
@@ -1458,7 +1491,6 @@
mPrimaryBouncerInteractor.notifyKeyguardAuthenticatedBiometrics(strongAuth);
if (mAlternateBouncerInteractor.isVisibleState()) {
- hideAlternateBouncer(false);
executeAfterKeyguardGoneAction();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index b5ab4e3..5d27467 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -61,7 +61,7 @@
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationClickNotifier;
@@ -115,7 +115,7 @@
private final Lazy<AssistManager> mAssistManagerLazy;
private final NotificationRemoteInputManager mRemoteInputManager;
private final NotificationLockscreenUserManager mLockscreenUserManager;
- private final com.android.systemui.shade.ShadeController mShadeController;
+ private final ShadeController mShadeController;
private final KeyguardStateController mKeyguardStateController;
private final LockPatternUtils mLockPatternUtils;
private final StatusBarRemoteInputCallback mStatusBarRemoteInputCallback;
@@ -126,7 +126,7 @@
private final StatusBarNotificationActivityStarterLogger mLogger;
private final NotificationPresenter mPresenter;
- private final ShadeViewController mShadeViewController;
+ private final PanelExpansionInteractor mPanelExpansionInteractor;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final ActivityTransitionAnimator mActivityTransitionAnimator;
private final NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
@@ -163,7 +163,7 @@
StatusBarNotificationActivityStarterLogger logger,
OnUserInteractionCallback onUserInteractionCallback,
NotificationPresenter presenter,
- ShadeViewController shadeViewController,
+ PanelExpansionInteractor panelExpansionInteractor,
NotificationShadeWindowController notificationShadeWindowController,
ActivityTransitionAnimator activityTransitionAnimator,
ShadeAnimationInteractor shadeAnimationInteractor,
@@ -192,13 +192,13 @@
mLockPatternUtils = lockPatternUtils;
mStatusBarRemoteInputCallback = remoteInputCallback;
mActivityIntentHelper = activityIntentHelper;
+ mPanelExpansionInteractor = panelExpansionInteractor;
mNotificationShadeWindowController = notificationShadeWindowController;
mShadeAnimationInteractor = shadeAnimationInteractor;
mMetricsLogger = metricsLogger;
mLogger = logger;
mOnUserInteractionCallback = onUserInteractionCallback;
mPresenter = presenter;
- mShadeViewController = shadeViewController;
mActivityTransitionAnimator = activityTransitionAnimator;
mNotificationAnimationProvider = notificationAnimationProvider;
mPowerInteractor = powerInteractor;
@@ -296,7 +296,7 @@
}
// Always defer the keyguard dismiss when animating.
- return animate || !mShadeViewController.isFullyCollapsed();
+ return animate || !mPanelExpansionInteractor.isFullyCollapsed();
}
private void handleNotificationClickAfterPanelCollapsed(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 8e9c038..5a3ae73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -44,6 +44,7 @@
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -87,6 +88,7 @@
private final NotificationMediaManager mMediaManager;
private final NotificationGutsManager mGutsManager;
private final ShadeViewController mNotificationPanel;
+ private final PanelExpansionInteractor mPanelExpansionInteractor;
private final HeadsUpManager mHeadsUpManager;
private final AboveShelfObserver mAboveShelfObserver;
private final DozeScrimController mDozeScrimController;
@@ -108,6 +110,7 @@
StatusBarNotificationPresenter(
Context context,
ShadeViewController panel,
+ PanelExpansionInteractor panelExpansionInteractor,
QuickSettingsController quickSettingsController,
HeadsUpManager headsUp,
NotificationShadeWindowView statusBarWindow,
@@ -134,6 +137,7 @@
mActivityStarter = activityStarter;
mKeyguardStateController = keyguardStateController;
mNotificationPanel = panel;
+ mPanelExpansionInteractor = panelExpansionInteractor;
mQsController = quickSettingsController;
mHeadsUpManager = headsUp;
mDynamicPrivacyController = dynamicPrivacyController;
@@ -202,7 +206,7 @@
@Override
public boolean isCollapsing() {
- return mNotificationPanel.isCollapsing()
+ return mPanelExpansionInteractor.isCollapsing()
|| mNotificationShadeWindowController.isLaunchingActivity();
}
@@ -232,7 +236,7 @@
@Override
public boolean isPresenterFullyCollapsed() {
- return mNotificationPanel.isFullyCollapsed();
+ return mPanelExpansionInteractor.isFullyCollapsed();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 88374d6..67d2299 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -12,16 +12,19 @@
import android.view.Surface
import android.view.View
import android.view.WindowManager.fixScale
+import com.android.app.animation.Interpolators
+import com.android.app.tracing.namedRunnable
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
import com.android.systemui.DejankUtils
-import com.android.app.animation.Interpolators
+import com.android.systemui.Flags.lightRevealMigration
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.statusbar.CircleReveal
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -32,9 +35,8 @@
import com.android.systemui.statusbar.notification.stack.AnimationProperties
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.Flags.lightRevealMigration
-import com.android.app.tracing.namedRunnable
import com.android.systemui.util.settings.GlobalSettings
+import dagger.Lazy
import javax.inject.Inject
/**
@@ -60,14 +62,15 @@
private val context: Context,
private val wakefulnessLifecycle: WakefulnessLifecycle,
private val statusBarStateControllerImpl: StatusBarStateControllerImpl,
- private val keyguardViewMediatorLazy: dagger.Lazy<KeyguardViewMediator>,
+ private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>,
private val keyguardStateController: KeyguardStateController,
- private val dozeParameters: dagger.Lazy<DozeParameters>,
+ private val dozeParameters: Lazy<DozeParameters>,
private val globalSettings: GlobalSettings,
- private val notifShadeWindowControllerLazy: dagger.Lazy<NotificationShadeWindowController>,
+ private val notifShadeWindowControllerLazy: Lazy<NotificationShadeWindowController>,
private val interactionJankMonitor: InteractionJankMonitor,
private val powerManager: PowerManager,
- private val handler: Handler = Handler()
+ private val panelExpansionInteractorLazy: Lazy<PanelExpansionInteractor>,
+ private val handler: Handler = Handler(),
) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
private lateinit var centralSurfaces: CentralSurfaces
private lateinit var shadeViewController: ShadeViewController
@@ -346,7 +349,7 @@
// already expanded and showing notifications/QS, the animation looks really messy. For now,
// disable it if the notification panel is expanded.
if ((!this::centralSurfaces.isInitialized ||
- shadeViewController.isPanelExpanded) &&
+ panelExpansionInteractorLazy.get().isPanelExpanded) &&
// Status bar might be expanded because we have started
// playing the animation already
!isAnimationPlaying()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
index f4e3eab..3b2930f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
@@ -18,6 +18,7 @@
import android.os.PersistableBundle
import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
+import android.telephony.CarrierConfigManager.KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT
import android.telephony.CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.flow.MutableStateFlow
@@ -42,10 +43,11 @@
* using the default config for logging purposes.
*
* NOTE to add new keys to be tracked:
- * 1. Define a new `private val` wrapping the key using [BooleanCarrierConfig]
- * 2. Define a public `val` exposing the wrapped flow using [BooleanCarrierConfig.config]
- * 3. Add the new [BooleanCarrierConfig] to the list of tracked configs, so they are properly
- * updated when a new carrier config comes down
+ * 1. Define a new `private val` wrapping the key using [BooleanCarrierConfig] or [IntCarrierConfig]
+ * 2. Define a public `val` exposing the wrapped flow using [BooleanCarrierConfig.config] or
+ * [IntCarrierConfig.config]
+ * 3. Add the new wrapped public flow to the list of tracked configs, so they are properly updated
+ * when a new carrier config comes down
*/
class SystemUiCarrierConfig
internal constructor(
@@ -66,10 +68,16 @@
/** Flow tracking the [KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL] config */
val showOperatorNameInStatusBar: StateFlow<Boolean> = showOperatorName.config
+ private val satelliteHysteresisSeconds =
+ IntCarrierConfig(KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT, defaultConfig)
+ /** Flow tracking the [KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT] config */
+ val satelliteConnectionHysteresisSeconds: StateFlow<Int> = satelliteHysteresisSeconds.config
+
private val trackedConfigs =
listOf(
inflateSignalStrength,
showOperatorName,
+ satelliteHysteresisSeconds,
)
/** Ingest a new carrier config, and switch all of the tracked keys over to the new values */
@@ -90,15 +98,19 @@
override fun toString(): String = trackedConfigs.joinToString { it.toString() }
}
+interface CarrierConfig {
+ fun update(config: PersistableBundle)
+}
+
/** Extracts [key] from the carrier config, and stores it in a flow */
private class BooleanCarrierConfig(
val key: String,
defaultConfig: PersistableBundle,
-) {
+) : CarrierConfig {
private val _configValue = MutableStateFlow(defaultConfig.getBoolean(key))
val config = _configValue.asStateFlow()
- fun update(config: PersistableBundle) {
+ override fun update(config: PersistableBundle) {
_configValue.value = config.getBoolean(key)
}
@@ -106,3 +118,20 @@
return "$key=${config.value}"
}
}
+
+/** Extracts [key] from the carrier config, and stores it in a flow */
+private class IntCarrierConfig(
+ val key: String,
+ defaultConfig: PersistableBundle,
+) : CarrierConfig {
+ private val _configValue = MutableStateFlow(defaultConfig.getInt(key))
+ val config = _configValue.asStateFlow()
+
+ override fun update(config: PersistableBundle) {
+ _configValue.value = config.getInt(key)
+ }
+
+ override fun toString(): String {
+ return "$key=${config.value}"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 8f3b0e7..8f00b43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -141,6 +141,9 @@
*/
val hasPrioritizedNetworkCapabilities: StateFlow<Boolean>
+ /** Duration in seconds of the hysteresis to use when losing satellite connection. */
+ val satelliteConnectionHysteresisSeconds: StateFlow<Int>
+
/**
* True if this connection is in emergency callback mode.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
index 3cb138bd..af34a57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
@@ -206,6 +206,8 @@
override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false)
+ override val satelliteConnectionHysteresisSeconds = MutableStateFlow(0)
+
override suspend fun isInEcmMode(): Boolean = false
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
index f8858c5..2bc3bcb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
@@ -186,6 +186,9 @@
*/
override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false).asStateFlow()
+ /** Non-applicable to carrier merged connections. */
+ override val satelliteConnectionHysteresisSeconds = MutableStateFlow(0).asStateFlow()
+
override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled
override suspend fun isInEcmMode(): Boolean =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index a124196..60b8599 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -334,6 +334,15 @@
activeRepo.value.hasPrioritizedNetworkCapabilities.value,
)
+ override val satelliteConnectionHysteresisSeconds =
+ activeRepo
+ .flatMapLatest { it.satelliteConnectionHysteresisSeconds }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ activeRepo.value.satelliteConnectionHysteresisSeconds.value
+ )
+
override suspend fun isInEcmMode(): Boolean = activeRepo.value.isInEcmMode()
class Factory
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 77fd6be..f01ac0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -442,6 +442,9 @@
.flowOn(bgDispatcher)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ override val satelliteConnectionHysteresisSeconds: StateFlow<Int> =
+ systemUiCarrierConfig.satelliteConnectionHysteresisSeconds
+
class Factory
@Inject
constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index adcf736..9d194cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -35,18 +35,25 @@
import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.util.kotlin.pairwiseBy
+import kotlin.time.DurationUnit
+import kotlin.time.toDuration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
interface MobileIconInteractor {
/** The table log created for this connection */
@@ -262,6 +269,43 @@
MutableStateFlow(false).asStateFlow()
}
+ private val hysteresisActive = MutableStateFlow(false)
+
+ private val isNonTerrestrialWithHysteresis: StateFlow<Boolean> =
+ combine(isNonTerrestrial, hysteresisActive) { isNonTerrestrial, hysteresisActive ->
+ if (hysteresisActive) {
+ true
+ } else {
+ isNonTerrestrial
+ }
+ }
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnName = "isNonTerrestrialWithHysteresis",
+ columnPrefix = "",
+ initialValue = Flags.carrierEnabledSatelliteFlag(),
+ )
+ .stateIn(scope, SharingStarted.Eagerly, Flags.carrierEnabledSatelliteFlag())
+
+ private val lostSatelliteConnection =
+ isNonTerrestrial.pairwiseBy { old, new -> hysteresisActive.value = old && !new }
+
+ init {
+ scope.launch { lostSatelliteConnection.collect() }
+ scope.launch {
+ hysteresisActive.collectLatest {
+ if (it) {
+ delay(
+ connectionRepository.satelliteConnectionHysteresisSeconds.value.toDuration(
+ DurationUnit.SECONDS
+ )
+ )
+ hysteresisActive.value = false
+ }
+ }
+ }
+ }
+
override val isRoaming: StateFlow<Boolean> =
combine(
connectionRepository.carrierNetworkChangeActive,
@@ -360,7 +404,7 @@
showExclamationMark.value,
carrierNetworkChangeActive.value,
)
- isNonTerrestrial
+ isNonTerrestrialWithHysteresis
.flatMapLatest { ntn ->
if (ntn) {
satelliteIcon
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
new file mode 100644
index 0000000..55a0f59
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -0,0 +1,301 @@
+/*
+ * 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.policy
+
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
+import com.android.systemui.statusbar.policy.BaseHeadsUpManager.HeadsUpEntry
+import javax.inject.Inject
+
+/*
+ * Control when heads up notifications show during an avalanche where notifications arrive in fast
+ * succession, by delaying visual listener side effects and removal handling from BaseHeadsUpManager
+ */
+@SysUISingleton
+class AvalancheController @Inject constructor() {
+
+ private val tag = "AvalancheController"
+ private val debug = false
+
+ // HUN showing right now, in the floating state where full shade is hidden, on launcher or AOD
+ @VisibleForTesting var headsUpEntryShowing: HeadsUpEntry? = null
+
+ // List of runnables to run for the HUN showing right now
+ private var headsUpEntryShowingRunnableList: MutableList<Runnable> = ArrayList()
+
+ // HeadsUpEntry waiting to show
+ // Use sortable list instead of priority queue for debugging
+ private val nextList: MutableList<HeadsUpEntry> = ArrayList()
+
+ // Map of HeadsUpEntry waiting to show, and runnables to run when it shows.
+ // Use HashMap instead of SortedMap for faster lookup, and also because the ordering
+ // provided by HeadsUpEntry.compareTo is not consistent over time or with HeadsUpEntry.equals
+ @VisibleForTesting var nextMap: MutableMap<HeadsUpEntry, MutableList<Runnable>> = HashMap()
+
+ // Map of Runnable to label for debugging only
+ private val debugRunnableLabelMap: MutableMap<Runnable, String> = HashMap()
+
+ // HeadsUpEntry we did not show at all because they are not the top priority hun in their batch
+ // For debugging only
+ @VisibleForTesting var debugDropSet: MutableSet<HeadsUpEntry> = HashSet()
+
+ /**
+ * Run or delay Runnable for given HeadsUpEntry
+ */
+ fun update(entry: HeadsUpEntry, runnable: Runnable, label: String) {
+ if (!NotificationThrottleHun.isEnabled) {
+ runnable.run()
+ return
+ }
+ val fn = "[$label] => AvalancheController.update ${getKey(entry)}"
+
+ if (debug) {
+ debugRunnableLabelMap[runnable] = label
+ }
+
+ if (isShowing(entry)) {
+ log {"$fn => [update showing]" }
+ runnable.run()
+ } else if (entry in nextMap) {
+ log { "$fn => [update next]" }
+ nextMap[entry]?.add(runnable)
+ } else if (headsUpEntryShowing == null) {
+ log { "$fn => [showNow]" }
+ showNow(entry, arrayListOf(runnable))
+ } else {
+ // Clean up invalid state when entry is in list but not map and vice versa
+ if (entry in nextMap) nextMap.remove(entry)
+ if (entry in nextList) nextList.remove(entry)
+
+ addToNext(entry, runnable)
+
+ // Shorten headsUpEntryShowing display time
+ val nextIndex = nextList.indexOf(entry)
+ val isOnlyNextEntry = nextIndex == 0 && nextList.size == 1
+ if (isOnlyNextEntry) {
+ // HeadsUpEntry.updateEntry recursively calls AvalancheController#update
+ // and goes to the isShowing case above
+ headsUpEntryShowing!!.updateEntry(false, "avalanche duration update")
+ }
+ }
+ logState("after $fn")
+ }
+
+ @VisibleForTesting
+ fun addToNext(entry: HeadsUpEntry, runnable: Runnable) {
+ nextMap[entry] = arrayListOf(runnable)
+ nextList.add(entry)
+ }
+
+ /**
+ * Run or ignore Runnable for given HeadsUpEntry. If entry was never shown, ignore and delete
+ * all Runnables associated with that entry.
+ */
+ fun delete(entry: HeadsUpEntry, runnable: Runnable, label: String) {
+ if (!NotificationThrottleHun.isEnabled) {
+ runnable.run()
+ return
+ }
+ val fn = "[$label] => AvalancheController.delete " + getKey(entry)
+
+ if (entry in nextMap) {
+ log { "$fn => [remove from next]" }
+ if (entry in nextMap) nextMap.remove(entry)
+ if (entry in nextList) nextList.remove(entry)
+ } else if (entry in debugDropSet) {
+ log { "$fn => [remove from dropset]" }
+ debugDropSet.remove(entry)
+ } else if (isShowing(entry)) {
+ log { "$fn => [remove showing ${getKey(entry)}]" }
+ runnable.run()
+ showNext()
+ } else {
+ log { "$fn => [removing untracked ${getKey(entry)}]" }
+ }
+ logState("after $fn")
+ }
+
+ /**
+ * Returns duration based on
+ * 1) Whether HeadsUpEntry is the last one tracked byAvalancheController
+ * 2) The priority of the top HUN in the next batch Used by
+ * BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration.
+ */
+ fun getDurationMs(entry: HeadsUpEntry, autoDismissMs: Int): Int {
+ if (!NotificationThrottleHun.isEnabled) {
+ // Use default duration, like we did before AvalancheController existed
+ return autoDismissMs
+ }
+ val showingList: MutableList<HeadsUpEntry> = mutableListOf()
+ headsUpEntryShowing?.let { showingList.add(it) }
+
+ val entryList = showingList + nextList
+ if (entryList.indexOf(entry) == entryList.size - 1) {
+ // Use default duration if last entry
+ return autoDismissMs
+ }
+
+ nextList.sort()
+ val nextEntry = nextList[0]
+
+ if (nextEntry.compareNonTimeFields(entry) == -1) {
+ // Next entry is higher priority
+ return 500
+ } else if (nextEntry.compareNonTimeFields(entry) == 0) {
+ // Next entry is same priority
+ return 1000
+ } else {
+ return autoDismissMs
+ }
+ }
+
+ /**
+ * Return true if entry is waiting to show.
+ */
+ fun isWaiting(key: String): Boolean {
+ if (!NotificationThrottleHun.isEnabled) {
+ return false
+ }
+ for (entry in nextMap.keys) {
+ if (entry.mEntry?.key.equals(key)) {
+ return true
+ }
+ }
+ return false
+ }
+
+ /**
+ * Return list of keys for huns waiting
+ */
+ fun getWaitingKeys(): MutableList<String> {
+ if (!NotificationThrottleHun.isEnabled) {
+ return mutableListOf()
+ }
+ val keyList = mutableListOf<String>()
+ for (entry in nextMap.keys) {
+ entry.mEntry?.let { keyList.add(entry.mEntry!!.key) }
+ }
+ return keyList
+ }
+
+ private fun isShowing(entry: HeadsUpEntry): Boolean {
+ return headsUpEntryShowing != null && entry.mEntry?.key == headsUpEntryShowing?.mEntry?.key
+ }
+
+ private fun showNow(entry: HeadsUpEntry, runnableList: MutableList<Runnable>) {
+ log { "show " + getKey(entry) + " backlog size: " + runnableList.size }
+
+ headsUpEntryShowing = entry
+
+ runnableList.forEach {
+ if (it in debugRunnableLabelMap) {
+ log { "run runnable from: ${debugRunnableLabelMap[it]}" }
+ }
+ it.run()
+ }
+ }
+
+ private fun showNext() {
+ log { "showNext" }
+ headsUpEntryShowing = null
+
+ if (nextList.isEmpty()) {
+ log { "no more to show!" }
+ return
+ }
+
+ // Only show first (top priority) entry in next batch
+ nextList.sort()
+ headsUpEntryShowing = nextList[0]
+ headsUpEntryShowingRunnableList = nextMap[headsUpEntryShowing]!!
+
+ // Remove runnable labels for dropped huns
+ val listToDrop = nextList.subList(1, nextList.size)
+ if (debug) {
+ // Clear runnable labels
+ for (e in listToDrop) {
+ val runnableList = nextMap[e]!!
+ for (r in runnableList) {
+ debugRunnableLabelMap.remove(r)
+ }
+ }
+ debugDropSet.addAll(listToDrop)
+ }
+
+ clearNext()
+ showNow(headsUpEntryShowing!!, headsUpEntryShowingRunnableList)
+ }
+
+ fun clearNext() {
+ nextList.clear()
+ nextMap.clear()
+ }
+
+ // Methods below are for logging only ==========================================================
+
+ private inline fun log(s: () -> String) {
+ if (debug) {
+ Log.d(tag, s())
+ }
+ }
+
+ // TODO(b/315362456) expose as dumpable for bugreports
+ private fun logState(reason: String) {
+ log { "state $reason" }
+ log { "showing: " + getKey(headsUpEntryShowing) }
+ log { "next list: $nextListStr map: $nextMapStr" }
+ log { "drop: $dropSetStr" }
+ }
+
+ private val dropSetStr: String
+ get() {
+ val queue = ArrayList<String>()
+ for (entry in debugDropSet) {
+ queue.add(getKey(entry))
+ }
+ return java.lang.String.join(" ", queue)
+ }
+
+ private val nextListStr: String
+ get() {
+ val queue = ArrayList<String>()
+ for (entry in nextList) {
+ queue.add(getKey(entry))
+ }
+ return java.lang.String.join(" ", queue)
+ }
+
+ private val nextMapStr: String
+ get() {
+ val queue = ArrayList<String>()
+ for (entry in nextMap.keys) {
+ queue.add(getKey(entry))
+ }
+ return java.lang.String.join(" ", queue)
+ }
+
+ fun getKey(entry: HeadsUpEntry?): String {
+ if (entry == null) {
+ return "null"
+ }
+ if (entry.mEntry == null) {
+ return entry.toString()
+ }
+ return entry.mEntry!!.key
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index 530e49c..50de3cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -45,6 +45,8 @@
import com.android.systemui.util.time.SystemClock;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
import java.util.stream.Stream;
/**
@@ -68,6 +70,7 @@
private final AccessibilityManagerWrapper mAccessibilityMgr;
private final UiEventLogger mUiEventLogger;
+ private final AvalancheController mAvalancheController;
protected final SystemClock mSystemClock;
protected final ArrayMap<String, HeadsUpEntry> mHeadsUpEntryMap = new ArrayMap<>();
@@ -100,13 +103,15 @@
SystemClock systemClock,
@Main DelayableExecutor executor,
AccessibilityManagerWrapper accessibilityManagerWrapper,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ AvalancheController avalancheController) {
mLogger = logger;
mExecutor = executor;
mSystemClock = systemClock;
mContext = context;
mAccessibilityMgr = accessibilityManagerWrapper;
mUiEventLogger = uiEventLogger;
+ mAvalancheController = avalancheController;
Resources resources = context.getResources();
mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
mStickyForSomeTimeAutoDismissTime = resources.getInteger(
@@ -157,18 +162,26 @@
*/
@Override
public void showNotification(@NonNull NotificationEntry entry) {
- mLogger.logShowNotification(entry);
-
- // Add new entry and begin managing it
HeadsUpEntry headsUpEntry = createHeadsUpEntry();
- headsUpEntry.setEntry(entry);
- mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry);
- onEntryAdded(headsUpEntry);
- entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- entry.setIsHeadsUpEntry(true);
- updateNotification(entry.getKey(), true /* shouldHeadsUpAgain */);
- entry.setInterruption();
+ // Attach NotificationEntry for AvalancheController to log key and
+ // record mPostTime for AvalancheController sorting
+ headsUpEntry.setEntry(entry);
+
+ Runnable runnable = () -> {
+ // TODO(b/315362456) log outside runnable too
+ mLogger.logShowNotification(entry);
+
+ // Add new entry and begin managing it
+ mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry);
+ onEntryAdded(headsUpEntry);
+ entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ entry.setIsHeadsUpEntry(true);
+
+ updateNotificationInternal(entry.getKey(), true /* shouldHeadsUpAgain */);
+ entry.setInterruption();
+ };
+ mAvalancheController.update(headsUpEntry, runnable, "showNotification");
}
/**
@@ -181,6 +194,11 @@
@Override
public boolean removeNotification(@NonNull String key, boolean releaseImmediately) {
mLogger.logRemoveNotification(key, releaseImmediately);
+
+ if (mAvalancheController.isWaiting(key)) {
+ removeEntry(key);
+ return true;
+ }
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
if (headsUpEntry == null) {
return true;
@@ -203,6 +221,14 @@
*/
public void updateNotification(@NonNull String key, boolean shouldHeadsUpAgain) {
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+ Runnable runnable = () -> {
+ updateNotificationInternal(key, shouldHeadsUpAgain);
+ };
+ mAvalancheController.update(headsUpEntry, runnable, "updateNotification");
+ }
+
+ private void updateNotificationInternal(@NonNull String key, boolean shouldHeadsUpAgain) {
+ HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
mLogger.logUpdateNotification(key, shouldHeadsUpAgain, headsUpEntry != null);
if (headsUpEntry == null) {
// the entry was released before this update (i.e by a listener) This can happen
@@ -231,12 +257,16 @@
for (String key : keysToRemove) {
removeEntry(key);
}
+ for (String key : mAvalancheController.getWaitingKeys()) {
+ removeEntry(key);
+ }
}
/**
* Returns the entry if it is managed by this manager.
* @param key key of notification
* @return the entry
+ * TODO(b/315362456) See if caller needs to check AvalancheController waiting entries
*/
@Nullable
public NotificationEntry getEntry(@NonNull String key) {
@@ -251,6 +281,7 @@
@NonNull
@Override
public Stream<NotificationEntry> getAllEntries() {
+ // TODO(b/315362456) See if callers need to check AvalancheController
return mHeadsUpEntryMap.values().stream().map(headsUpEntry -> headsUpEntry.mEntry);
}
@@ -267,7 +298,7 @@
* @return true if the notification is managed by this manager
*/
public boolean isHeadsUpEntry(@NonNull String key) {
- return mHeadsUpEntryMap.containsKey(key);
+ return mHeadsUpEntryMap.containsKey(key) || mAvalancheController.isWaiting(key);
}
/**
@@ -331,7 +362,7 @@
* Manager-specific logic that should occur when an entry is added.
* @param headsUpEntry entry added
*/
- protected void onEntryAdded(HeadsUpEntry headsUpEntry) {
+ void onEntryAdded(HeadsUpEntry headsUpEntry) {
NotificationEntry entry = headsUpEntry.mEntry;
entry.setHeadsUp(true);
@@ -349,20 +380,24 @@
*/
protected final void removeEntry(@NonNull String key) {
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
- if (headsUpEntry == null) {
- return;
- }
- NotificationEntry entry = headsUpEntry.mEntry;
- // If the notification is animating, we will remove it at the end of the animation.
- if (entry != null && entry.isExpandAnimationRunning()) {
- return;
- }
- entry.demoteStickyHun();
- mHeadsUpEntryMap.remove(key);
- onEntryRemoved(headsUpEntry);
- entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- headsUpEntry.reset();
+ Runnable runnable = () -> {
+ if (headsUpEntry == null) {
+ return;
+ }
+ NotificationEntry entry = headsUpEntry.mEntry;
+
+ // If the notification is animating, we will remove it at the end of the animation.
+ if (entry != null && entry.isExpandAnimationRunning()) {
+ return;
+ }
+ entry.demoteStickyHun();
+ mHeadsUpEntryMap.remove(key);
+ onEntryRemoved(headsUpEntry);
+ entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ headsUpEntry.reset();
+ };
+ mAvalancheController.delete(headsUpEntry, runnable, "removeEntry");
}
/**
@@ -380,7 +415,7 @@
}
}
- protected void updatePinnedMode() {
+ private void updatePinnedMode() {
boolean hasPinnedNotification = hasPinnedNotificationInternal();
if (hasPinnedNotification == mHasPinnedNotification) {
return;
@@ -416,7 +451,9 @@
* Snoozes all current Heads Up Notifications.
*/
public void snooze() {
- for (String key : mHeadsUpEntryMap.keySet()) {
+ List<String> keySet = new ArrayList<>(mHeadsUpEntryMap.keySet());
+ keySet.addAll(mAvalancheController.getWaitingKeys());
+ for (String key : keySet) {
HeadsUpEntry entry = getHeadsUpEntry(key);
String packageName = entry.mEntry.getSbn().getPackageName();
String snoozeKey = snoozeKey(packageName, mUser);
@@ -432,6 +469,7 @@
@Nullable
protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) {
+ // TODO(b/315362456) See if callers need to check AvalancheController
return (HeadsUpEntry) mHeadsUpEntryMap.get(key);
}
@@ -515,18 +553,22 @@
*/
public void unpinAll(boolean userUnPinned) {
for (String key : mHeadsUpEntryMap.keySet()) {
- HeadsUpEntry entry = getHeadsUpEntry(key);
- setEntryPinned(entry, false /* isPinned */);
- // maybe it got un sticky
- entry.updateEntry(false /* updatePostTime */, "unpinAll");
+ HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
- // when the user unpinned all of HUNs by moving one HUN, all of HUNs should not stay
- // on the screen.
- if (userUnPinned && entry.mEntry != null) {
- if (entry.mEntry.mustStayOnScreen()) {
- entry.mEntry.setHeadsUpIsVisible();
+ Runnable runnable = () -> {
+ setEntryPinned(headsUpEntry, false /* isPinned */);
+ // maybe it got un sticky
+ headsUpEntry.updateEntry(false /* updatePostTime */, "unpinAll");
+
+ // when the user unpinned all of HUNs by moving one HUN, all of HUNs should not stay
+ // on the screen.
+ if (userUnPinned && headsUpEntry.mEntry != null) {
+ if (headsUpEntry.mEntry.mustStayOnScreen()) {
+ headsUpEntry.mEntry.setHeadsUpIsVisible();
+ }
}
- }
+ };
+ mAvalancheController.delete(headsUpEntry, runnable, "unpinAll");
}
}
@@ -606,6 +648,7 @@
*/
@Override
public boolean isSticky(String key) {
+ // TODO(b/315362456) See if callers need to check AvalancheController
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
if (headsUpEntry != null) {
return headsUpEntry.isSticky();
@@ -633,9 +676,10 @@
/**
* This represents a notification and how long it is in a heads up mode. It also manages its
- * lifecycle automatically when created.
+ * lifecycle automatically when created. This class is public because it is exposed by methods
+ * of AvalancheController that take it as param.
*/
- protected class HeadsUpEntry implements Comparable<HeadsUpEntry> {
+ public class HeadsUpEntry implements Comparable<HeadsUpEntry> {
public boolean mRemoteInputActive;
public boolean mUserActionMayIndirectlyRemove;
@@ -672,27 +716,41 @@
}
/**
+ * An interface that returns the amount of time left this HUN should show.
+ */
+ interface FinishTimeUpdater {
+ long updateAndGetTimeRemaining();
+ }
+
+ /**
* Updates an entry's removal time.
* @param updatePostTime whether or not to refresh the post time
*/
public void updateEntry(boolean updatePostTime, @Nullable String reason) {
- mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
+ Runnable runnable = () -> {
+ mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
- final long now = mSystemClock.elapsedRealtime();
- mEarliestRemovalTime = now + mMinimumDisplayTime;
+ final long now = mSystemClock.elapsedRealtime();
+ mEarliestRemovalTime = now + mMinimumDisplayTime;
- if (updatePostTime) {
- mPostTime = Math.max(mPostTime, now);
- }
+ if (updatePostTime) {
+ mPostTime = Math.max(mPostTime, now);
+ }
+ };
+ mAvalancheController.update(this, runnable, "updateEntry (updatePostTime)");
if (isSticky()) {
- removeAutoRemovalCallbacks("updateEntry (sticky)");
+ cancelAutoRemovalCallbacks("updateEntry (sticky)");
return;
}
- final long finishTime = calculateFinishTime();
- final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
- scheduleAutoRemovalCallback(timeLeft, "updateEntry (not sticky)");
+ FinishTimeUpdater finishTimeCalculator = () -> {
+ final long finishTime = calculateFinishTime();
+ final long now = mSystemClock.elapsedRealtime();
+ final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
+ return timeLeft;
+ };
+ scheduleAutoRemovalCallback(finishTimeCalculator, "updateEntry (not sticky)");
}
/**
@@ -718,7 +776,7 @@
return mEarliestRemovalTime < mSystemClock.elapsedRealtime();
}
- public int compareTo(@NonNull HeadsUpEntry headsUpEntry) {
+ public int compareNonTimeFields(HeadsUpEntry headsUpEntry) {
boolean isPinned = mEntry.isRowPinned();
boolean otherPinned = headsUpEntry.mEntry.isRowPinned();
if (isPinned && !otherPinned) {
@@ -748,7 +806,14 @@
} else if (!mRemoteInputActive && headsUpEntry.mRemoteInputActive) {
return 1;
}
+ return 0;
+ }
+ public int compareTo(@NonNull HeadsUpEntry headsUpEntry) {
+ int nonTimeCompareResult = compareNonTimeFields(headsUpEntry);
+ if (nonTimeCompareResult != 0) {
+ return nonTimeCompareResult;
+ }
if (mPostTime > headsUpEntry.mPostTime) {
return -1;
} else if (mPostTime == headsUpEntry.mPostTime) {
@@ -758,12 +823,31 @@
}
}
+ @Override
+ public int hashCode() {
+ if (mEntry == null) return super.hashCode();
+ int result = mEntry.getKey().hashCode();
+ result = 31 * result;
+ return result;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof HeadsUpEntry)) return false;
+ HeadsUpEntry otherHeadsUpEntry = (HeadsUpEntry) o;
+ if (mEntry != null && otherHeadsUpEntry.mEntry != null) {
+ return mEntry.getKey().equals(otherHeadsUpEntry.mEntry.getKey());
+ }
+ return false;
+ }
+
public void setExpanded(boolean expanded) {
this.mExpanded = expanded;
}
public void reset() {
- removeAutoRemovalCallbacks("reset()");
+ cancelAutoRemovalCallbacks("reset()");
mEntry = null;
mRemoveRunnable = null;
mExpanded = false;
@@ -773,37 +857,48 @@
/**
* Clear any pending removal runnables.
*/
- public void removeAutoRemovalCallbacks(@Nullable String reason) {
- final boolean removed = removeAutoRemovalCallbackInternal();
+ public void cancelAutoRemovalCallbacks(@Nullable String reason) {
+ Runnable runnable = () -> {
+ final boolean removed = cancelAutoRemovalCallbackInternal();
- if (removed) {
- mLogger.logAutoRemoveCanceled(mEntry, reason);
- }
+ if (removed) {
+ mLogger.logAutoRemoveCanceled(mEntry, reason);
+ }
+ };
+ mAvalancheController.update(this, runnable,
+ reason + " removeAutoRemovalCallbacks");
}
- public void scheduleAutoRemovalCallback(long delayMillis, @NonNull String reason) {
- if (mRemoveRunnable == null) {
- Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set");
- return;
- }
+ public void scheduleAutoRemovalCallback(FinishTimeUpdater finishTimeCalculator,
+ @NonNull String reason) {
- final boolean removed = removeAutoRemovalCallbackInternal();
+ Runnable runnable = () -> {
+ long delayMs = finishTimeCalculator.updateAndGetTimeRemaining();
- if (removed) {
- mLogger.logAutoRemoveRescheduled(mEntry, delayMillis, reason);
- } else {
- mLogger.logAutoRemoveScheduled(mEntry, delayMillis, reason);
- }
+ if (mRemoveRunnable == null) {
+ Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set");
+ return;
+ }
- mCancelRemoveRunnable = mExecutor.executeDelayed(mRemoveRunnable,
- delayMillis);
+ final boolean deletedExistingRemovalRunnable = cancelAutoRemovalCallbackInternal();
+ mCancelRemoveRunnable = mExecutor.executeDelayed(mRemoveRunnable,
+ delayMs);
+
+ if (deletedExistingRemovalRunnable) {
+ mLogger.logAutoRemoveRescheduled(mEntry, delayMs, reason);
+ } else {
+ mLogger.logAutoRemoveScheduled(mEntry, delayMs, reason);
+ }
+ };
+ mAvalancheController.update(this, runnable,
+ reason + " scheduleAutoRemovalCallback");
}
- public boolean removeAutoRemovalCallbackInternal() {
+ public boolean cancelAutoRemovalCallbackInternal() {
final boolean scheduled = (mCancelRemoveRunnable != null);
if (scheduled) {
- mCancelRemoveRunnable.run();
+ mCancelRemoveRunnable.run(); // Delete removal runnable from Executor queue
mCancelRemoveRunnable = null;
}
@@ -815,8 +910,12 @@
*/
public void removeAsSoonAsPossible() {
if (mRemoveRunnable != null) {
- final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
- scheduleAutoRemovalCallback(timeLeft, "removeAsSoonAsPossible");
+
+ FinishTimeUpdater finishTimeCalculator = () -> {
+ final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
+ return timeLeft;
+ };
+ scheduleAutoRemovalCallback(finishTimeCalculator, "removeAsSoonAsPossible");
}
}
@@ -834,9 +933,13 @@
* {@link SystemClock#elapsedRealtime()}
*/
protected long calculateFinishTime() {
- final long duration = getRecommendedHeadsUpTimeoutMs(
- isStickyForSomeTime() ? mStickyForSomeTimeAutoDismissTime : mAutoDismissTime);
-
+ int requestedTimeOutMs;
+ if (isStickyForSomeTime()) {
+ requestedTimeOutMs = mStickyForSomeTimeAutoDismissTime;
+ } else {
+ requestedTimeOutMs = mAvalancheController.getDurationMs(this, mAutoDismissTime);
+ }
+ final long duration = getRecommendedHeadsUpTimeoutMs(requestedTimeOutMs);
return mPostTime + duration;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java
index 422aa4d..de0eb49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java
@@ -16,12 +16,13 @@
package com.android.systemui.statusbar.policy;
+import android.annotation.NonNull;
import android.content.Context;
+import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceStateUtil;
import android.util.SparseIntArray;
-import androidx.annotation.NonNull;
-
import com.android.app.tracing.ListenersTracing;
import com.android.internal.R;
import com.android.systemui.dagger.SysUISingleton;
@@ -42,8 +43,9 @@
/** From androidx.window.common.COMMON_STATE_USE_BASE_STATE */
private static final int COMMON_STATE_USE_BASE_STATE = 1000;
private final List<Callback> mListeners = new ArrayList<>();
+ private final List<DeviceState> mSupportedStates;
+ private DeviceState mCurrentDeviceState;
private int mCurrentDevicePosture = DEVICE_POSTURE_UNKNOWN;
- private int mCurrentBasePosture = DEVICE_POSTURE_UNKNOWN;
private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray();
@@ -76,21 +78,17 @@
mDeviceStateToPostureMap.put(deviceState, posture);
}
+ mSupportedStates = deviceStateManager.getSupportedDeviceStates();
deviceStateManager.registerCallback(executor, new DeviceStateManager.DeviceStateCallback() {
@Override
- public void onStateChanged(int state) {
+ public void onDeviceStateChanged(@NonNull DeviceState state) {
+ mCurrentDeviceState = state;
Assert.isMainThread();
- mCurrentDevicePosture =
- mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN);
- sendUpdatePosture();
- }
-
- @Override
- public void onBaseStateChanged(int state) {
- Assert.isMainThread();
- mCurrentBasePosture = mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN);
-
- if (useBaseState()) {
+ int newDevicePosture =
+ mDeviceStateToPostureMap.get(state.getIdentifier(), DEVICE_POSTURE_UNKNOWN);
+ if (newDevicePosture != mCurrentDevicePosture
+ || newDevicePosture == COMMON_STATE_USE_BASE_STATE) {
+ mCurrentDevicePosture = newDevicePosture;
sendUpdatePosture();
}
}
@@ -120,7 +118,8 @@
@Override
public int getDevicePosture() {
if (useBaseState()) {
- return mCurrentBasePosture;
+ return DeviceStateUtil.calculateBaseStateIdentifier(mCurrentDeviceState,
+ mSupportedStates);
} else {
return mCurrentDevicePosture;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
index 3008c866d..88cf46a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
@@ -20,6 +20,7 @@
import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED;
import android.annotation.Nullable;
+import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Trace;
import android.util.IndentingPrintWriter;
@@ -120,18 +121,18 @@
mDeviceStateRotationLockSettingsManager.updateSetting(deviceState, isRotationLocked);
}
- private void updateDeviceState(int state) {
- mLogger.logUpdateDeviceState(mDeviceState, state);
- if (Trace.isEnabled()) {
- Trace.traceBegin(
- Trace.TRACE_TAG_APP, "updateDeviceState [state=" + state + "]");
- }
+ private void updateDeviceState(@NonNull DeviceState state) {
+ mLogger.logUpdateDeviceState(mDeviceState, state.getIdentifier());
try {
- if (mDeviceState == state) {
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(Trace.TRACE_TAG_APP,
+ "updateDeviceState [state=" + state.getIdentifier() + "]");
+ }
+ if (mDeviceState == state.getIdentifier()) {
return;
}
- readPersistedSetting("updateDeviceState", state);
+ readPersistedSetting("updateDeviceState", state.getIdentifier());
} finally {
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt
index 71bce5e..9d74c58 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt
@@ -18,11 +18,12 @@
import com.android.systemui.volume.panel.component.spatial.domain.interactor.SpatialAudioComponentInteractor
import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.combine
@VolumePanelScope
class SpatialAudioAvailabilityCriteria
@@ -31,5 +32,11 @@
ComponentAvailabilityCriteria {
override fun isAvailable(): Flow<Boolean> =
- interactor.isAvailable.map { it is SpatialAudioAvailabilityModel.SpatialAudio }
+ combine(interactor.isAvailable, interactor.isEnabled) { isAvailable, isEnabled ->
+ if (isAvailable is SpatialAudioAvailabilityModel.SpatialAudio) {
+ isEnabled !is SpatialAudioEnabledModel.Unknown
+ } else {
+ false
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
index 6032bfe..298ca67 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
@@ -18,8 +18,9 @@
import android.media.AudioDeviceAttributes
import android.media.AudioDeviceInfo
-import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.media.BluetoothMediaDevice
+import com.android.settingslib.media.MediaDevice
+import com.android.settingslib.media.PhoneMediaDevice
import com.android.settingslib.media.domain.interactor.SpatializerInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
@@ -54,12 +55,9 @@
private val currentAudioDeviceAttributes: StateFlow<AudioDeviceAttributes?> =
mediaOutputInteractor.currentConnectedDevice
.map { mediaDevice ->
- mediaDevice ?: return@map null
- val btDevice: CachedBluetoothDevice =
- (mediaDevice as? BluetoothMediaDevice)?.cachedDevice ?: return@map null
- btDevice.getAudioDeviceAttributes()
+ if (mediaDevice == null) builtinSpeaker else mediaDevice.getAudioDeviceAttributes()
}
- .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), builtinSpeaker)
/**
* Returns spatial audio availability model. It can be:
@@ -115,7 +113,7 @@
.stateIn(
coroutineScope,
SharingStarted.Eagerly,
- SpatialAudioEnabledModel.Disabled,
+ SpatialAudioEnabledModel.Unknown,
)
/**
@@ -137,34 +135,50 @@
changes.emit(Unit)
}
- private suspend fun CachedBluetoothDevice.getAudioDeviceAttributes(): AudioDeviceAttributes? {
- return listOf(
- AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_OUTPUT,
- AudioDeviceInfo.TYPE_BLE_HEADSET,
- address
- ),
- AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_OUTPUT,
- AudioDeviceInfo.TYPE_BLE_SPEAKER,
- address
- ),
- AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_OUTPUT,
- AudioDeviceInfo.TYPE_BLE_BROADCAST,
- address
- ),
- AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_OUTPUT,
- AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
- address
- ),
- AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_OUTPUT,
- AudioDeviceInfo.TYPE_HEARING_AID,
- address
- )
+ private suspend fun MediaDevice.getAudioDeviceAttributes(): AudioDeviceAttributes? {
+ when (this) {
+ is PhoneMediaDevice -> return builtinSpeaker
+ is BluetoothMediaDevice -> {
+ val device = cachedDevice ?: return null
+ return listOf(
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_HEADSET,
+ device.address,
+ ),
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_SPEAKER,
+ device.address,
+ ),
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_BROADCAST,
+ device.address,
+ ),
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+ device.address,
+ ),
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_HEARING_AID,
+ device.address,
+ )
+ )
+ .firstOrNull { spatializerInteractor.isSpatialAudioAvailable(it) }
+ }
+ else -> return null
+ }
+ }
+
+ private companion object {
+ val builtinSpeaker =
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+ ""
)
- .firstOrNull { spatializerInteractor.isSpatialAudioAvailable(it) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
index 9735e5c..4255a40 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
@@ -39,4 +39,7 @@
/** Head tracking is enabled. This also means that [SpatialAudioEnabled]. */
data object HeadTrackingEnabled : SpatialAudioEnabled
+
+ /** Spatial audio enabled state is unknown. */
+ data object Unknown : SpatialAudioEnabled
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt
index 0c91bbf..ecd89ea 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt
@@ -24,75 +24,24 @@
class VolumeSliderInteractor @Inject constructor() {
/** mimic percentage volume setting */
- val displayValueRange: ClosedFloatingPointRange<Float> = 0f..100f
+ private val displayValueRange: ClosedFloatingPointRange<Float> = 0f..100f
/**
* Translates [volume], that belongs to [volumeRange] to the value that belongs to
* [displayValueRange].
- *
- * [currentValue] is the raw value received from the slider. Returns [currentValue] when it
- * translates to the same volume as [volume] parameter. This ensures smooth slider experience
- * (avoids snapping when the user stops dragging).
*/
fun processVolumeToValue(
volume: Int,
volumeRange: ClosedRange<Int>,
- currentValue: Float?,
- isMuted: Boolean,
): Float {
- if (isMuted) {
- return 0f
- }
- val changedVolume: Int? = currentValue?.let { translateValueToVolume(it, volumeRange) }
- return if (volume != volumeRange.start && volume == changedVolume) {
- currentValue
- } else {
- translateToRange(
- currentValue = volume.toFloat(),
- currentRangeStart = volumeRange.start.toFloat(),
- currentRangeEnd = volumeRange.endInclusive.toFloat(),
- targetRangeStart = displayValueRange.start,
- targetRangeEnd = displayValueRange.endInclusive,
- )
- }
- }
-
- /** Translates [value] from [displayValueRange] to volume that has [volumeRange]. */
- fun translateValueToVolume(
- value: Float,
- volumeRange: ClosedRange<Int>,
- ): Int {
- return translateToRange(
- currentValue = value,
- currentRangeStart = displayValueRange.start,
- currentRangeEnd = displayValueRange.endInclusive,
- targetRangeStart = volumeRange.start.toFloat(),
- targetRangeEnd = volumeRange.endInclusive.toFloat(),
- )
- .toInt()
- }
-
- /**
- * Translates a value from one range to another.
- *
- * ```
- * Given: currentValue=3, currentRange=[0, 8], targetRange=[0, 100]
- * Result: 37.5
- * ```
- */
- private fun translateToRange(
- currentValue: Float,
- currentRangeStart: Float,
- currentRangeEnd: Float,
- targetRangeStart: Float,
- targetRangeEnd: Float,
- ): Float {
- val currentRangeLength: Float = (currentRangeEnd - currentRangeStart)
- val targetRangeLength: Float = targetRangeEnd - targetRangeStart
+ val currentRangeStart: Float = volumeRange.start.toFloat()
+ val targetRangeStart: Float = displayValueRange.start
+ val currentRangeLength: Float = (volumeRange.endInclusive.toFloat() - currentRangeStart)
+ val targetRangeLength: Float = displayValueRange.endInclusive - targetRangeStart
if (currentRangeLength == 0f || targetRangeLength == 0f) {
return 0f
}
- val volumeFraction: Float = (currentValue - currentRangeStart) / currentRangeLength
+ val volumeFraction: Float = (volume.toFloat() - currentRangeStart) / currentRangeLength
return targetRangeStart + volumeFraction * targetRangeLength
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index 532e517..1b73208 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -28,6 +28,7 @@
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -71,50 +72,49 @@
AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_media_unavailable,
)
- private var value = 0f
override val slider: StateFlow<SliderState> =
combine(
audioVolumeInteractor.getAudioStream(audioStream),
audioVolumeInteractor.canChangeVolume(audioStream),
audioVolumeInteractor.ringerMode,
) { model, isEnabled, ringerMode ->
- model.toState(value, isEnabled, ringerMode)
+ model.toState(isEnabled, ringerMode)
}
.stateIn(coroutineScope, SharingStarted.Eagerly, EmptyState)
- override fun onValueChangeFinished(state: SliderState, newValue: Float) {
+ override fun onValueChanged(state: SliderState, newValue: Float) {
val audioViewModel = state as? State
audioViewModel ?: return
coroutineScope.launch {
- value = newValue
- val volume =
- volumeSliderInteractor.translateValueToVolume(
- newValue,
- audioViewModel.audioStreamModel.volumeRange
- )
- audioVolumeInteractor.setVolume(audioStream, volume)
+ audioVolumeInteractor.setVolume(audioStream, newValue.roundToInt())
+ }
+ }
+
+ override fun toggleMuted(state: SliderState) {
+ val audioViewModel = state as? State
+ audioViewModel ?: return
+ coroutineScope.launch {
+ audioVolumeInteractor.setMuted(audioStream, !audioViewModel.audioStreamModel.isMuted)
}
}
private fun AudioStreamModel.toState(
- value: Float,
isEnabled: Boolean,
ringerMode: RingerMode,
): State {
return State(
- value =
- volumeSliderInteractor.processVolumeToValue(
- volume,
- volumeRange,
- value,
- isMuted,
+ value = volume.toFloat(),
+ valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
+ valueText =
+ SliderViewModel.formatValue(
+ volumeSliderInteractor.processVolumeToValue(volume, volumeRange)
),
- valueRange = volumeSliderInteractor.displayValueRange,
icon = getIcon(ringerMode),
label = labelsByStream[audioStream]?.let(context::getString)
?: error("No label for the stream: $audioStream"),
disabledMessage = disabledTextByStream[audioStream]?.let(context::getString),
isEnabled = isEnabled,
+ a11yStep = volumeRange.step,
audioStreamModel = this,
)
}
@@ -156,8 +156,10 @@
override val valueRange: ClosedFloatingPointRange<Float>,
override val icon: Icon,
override val label: String,
+ override val valueText: String,
override val disabledMessage: String?,
override val isEnabled: Boolean,
+ override val a11yStep: Int,
val audioStreamModel: AudioStreamModel,
) : SliderState
@@ -165,8 +167,10 @@
override val value: Float = 0f
override val valueRange: ClosedFloatingPointRange<Float> = 0f..1f
override val icon: Icon? = null
+ override val valueText: String = ""
override val label: String = ""
override val disabledMessage: String? = null
+ override val a11yStep: Int = 0
override val isEnabled: Boolean = true
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
index ae93826..86b2d73 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
@@ -26,8 +26,8 @@
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@@ -46,46 +46,46 @@
) : SliderViewModel {
private val volumeRange = 0..routingSession.routingSessionInfo.volumeMax
- private val value = MutableStateFlow(0f)
override val slider: StateFlow<SliderState> =
- combine(value, mediaOutputInteractor.currentConnectedDevice) { value, _ ->
- getCurrentState(value)
- }
- .stateIn(coroutineScope, SharingStarted.Eagerly, getCurrentState(value.value))
+ combine(mediaOutputInteractor.currentConnectedDevice) { _ -> getCurrentState() }
+ .stateIn(coroutineScope, SharingStarted.Eagerly, getCurrentState())
- override fun onValueChangeFinished(state: SliderState, newValue: Float) {
+ override fun onValueChanged(state: SliderState, newValue: Float) {
coroutineScope.launch {
- value.value = newValue
- castVolumeInteractor.setVolume(
- routingSession,
- volumeSliderInteractor.translateValueToVolume(newValue, volumeRange),
- )
+ castVolumeInteractor.setVolume(routingSession, newValue.roundToInt())
}
}
- private fun getCurrentState(value: Float): State {
- return State(
- value =
- volumeSliderInteractor.processVolumeToValue(
- volume = routingSession.routingSessionInfo.volume,
- volumeRange = volumeRange,
- currentValue = value,
- isMuted = false,
- ),
- valueRange = volumeSliderInteractor.displayValueRange,
+ override fun toggleMuted(state: SliderState) {
+ // do nothing because this action isn't supported for Cast sliders.
+ }
+
+ private fun getCurrentState(): State =
+ State(
+ value = routingSession.routingSessionInfo.volume.toFloat(),
+ valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
icon = Icon.Resource(R.drawable.ic_cast, null),
+ valueText =
+ SliderViewModel.formatValue(
+ volumeSliderInteractor.processVolumeToValue(
+ volume = routingSession.routingSessionInfo.volume,
+ volumeRange = volumeRange,
+ )
+ ),
label = context.getString(R.string.media_device_cast),
isEnabled = true,
+ a11yStep = 1
)
- }
private data class State(
override val value: Float,
override val valueRange: ClosedFloatingPointRange<Float>,
override val icon: Icon,
+ override val valueText: String,
override val label: String,
override val isEnabled: Boolean,
+ override val a11yStep: Int,
) : SliderState {
override val disabledMessage: String?
get() = null
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
index 6e9794b..b87d0a7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
@@ -27,7 +27,13 @@
val value: Float
val valueRange: ClosedFloatingPointRange<Float>
val icon: Icon?
- val label: String
- val disabledMessage: String?
val isEnabled: Boolean
+ val valueText: String
+ val label: String
+ /**
+ * A11y slider controls works by adjusting one step up or down. The default slider step isn't
+ * enough to trigger rounding to the correct value.
+ */
+ val a11yStep: Int
+ val disabledMessage: String?
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
index 0c4b322..e78f833 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
@@ -23,5 +23,12 @@
val slider: StateFlow<SliderState>
- fun onValueChangeFinished(state: SliderState, newValue: Float)
+ fun onValueChanged(state: SliderState, newValue: Float)
+
+ fun toggleMuted(state: SliderState)
+
+ companion object {
+
+ fun formatValue(value: Float): String = "%.0f".format(value)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
index 7fd9c8a..635191a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
@@ -46,12 +46,18 @@
!footerComponents.contains(it.key) &&
it.key != bottomBar
}
- val headerComponents = components.filter { headerComponents.contains(it.key) }
- val footerComponents = components.filter { footerComponents.contains(it.key) }
+ val headerComponents =
+ components
+ .filter { it.key in headerComponents }
+ .sortedBy { headerComponents.indexOf(it.key) }
+ val footerComponents =
+ components
+ .filter { it.key in footerComponents }
+ .sortedBy { footerComponents.indexOf(it.key) }
return ComponentsLayout(
- headerComponents = headerComponents.sortedBy { it.key },
+ headerComponents = headerComponents,
contentComponents = contentComponents.sortedBy { it.key },
- footerComponents = footerComponents.sortedBy { it.key },
+ footerComponents = footerComponents,
bottomBarComponent = components.find { it.key == bottomBar }
?: error(
"VolumePanelComponents.BOTTOM_BAR must be present in the default " +
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 7674fe9..7931fab 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -259,6 +259,11 @@
public void moveFocusedTaskToFullscreen(int displayId) {
splitScreen.goToFullscreenFromSplit();
}
+
+ @Override
+ public void setSplitscreenFocus(boolean leftOrTop) {
+ splitScreen.setSplitscreenFocus(leftOrTop);
+ }
});
}
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 5882b56..572a6c1 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -113,7 +113,7 @@
android:excludeFromRecents="true"
/>
- <activity android:name="com.android.systemui.screenshot.ScrollViewActivity"
+ <activity android:name="com.android.systemui.screenshot.scroll.ScrollViewActivity"
android:exported="false" />
<activity android:name="com.android.systemui.screenshot.RecyclerViewActivity"
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
index 90587d7..f1dfdf4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -20,16 +20,18 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
-import com.android.internal.jank.InteractionJankMonitor;
import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.power.data.repository.FakePowerRepository;
import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
import com.android.systemui.res.R;
@@ -46,6 +48,7 @@
public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase {
+ private KosmosJavaAdapter mKosmos;
@Mock protected KeyguardStatusView mKeyguardStatusView;
@Mock protected KeyguardSliceViewController mKeyguardSliceViewController;
@@ -57,7 +60,6 @@
@Mock protected ScreenOffAnimationController mScreenOffAnimationController;
@Mock protected KeyguardLogger mKeyguardLogger;
@Mock protected KeyguardStatusViewController mControllerMock;
- @Mock protected InteractionJankMonitor mInteractionJankMonitor;
@Mock protected ViewTreeObserver mViewTreeObserver;
@Mock protected DumpManager mDumpManager;
protected FakeKeyguardRepository mFakeKeyguardRepository;
@@ -71,6 +73,7 @@
@Before
public void setup() {
+ mKosmos = new KosmosJavaAdapter(this);
MockitoAnnotations.initMocks(this);
KeyguardInteractorFactory.WithDependencies deps = KeyguardInteractorFactory.create();
@@ -87,7 +90,7 @@
mDozeParameters,
mScreenOffAnimationController,
mKeyguardLogger,
- mInteractionJankMonitor,
+ mKosmos.getInteractionJankMonitor(),
deps.getKeyguardInteractor(),
mDumpManager,
PowerInteractorFactory.create(
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index e893eb1..11fe862 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -30,12 +30,14 @@
import static org.mockito.Mockito.when;
import android.animation.AnimatorTestRule;
+import android.platform.test.annotations.DisableFlags;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
import com.android.app.animation.Interpolators;
+import com.android.systemui.Flags;
import com.android.systemui.animation.ViewHierarchyAnimator;
import com.android.systemui.plugins.clocks.ClockConfig;
import com.android.systemui.plugins.clocks.ClockController;
@@ -80,6 +82,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
public void onLocaleListChangedNotifiesClockSwitchController() {
ArgumentCaptor<ConfigurationListener> configurationListenerArgumentCaptor =
ArgumentCaptor.forClass(ConfigurationListener.class);
@@ -239,6 +242,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
public void statusAreaHeightChange_animatesHeightOutputChange() {
// Init & Capture Layout Listener
mController.onInit();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 336a97e..fde45d3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -135,6 +135,7 @@
import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus;
import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserTracker;
@@ -195,7 +196,7 @@
DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, TEST_GROUP_UUID,
TEST_CARRIER_ID, PROFILE_CLASS_PROVISIONING);
private static final int FINGERPRINT_SENSOR_ID = 1;
-
+ private KosmosJavaAdapter mKosmos;
@Mock
private UserTracker mUserTracker;
@Mock
@@ -240,7 +241,6 @@
private AuthController mAuthController;
@Mock
private TelephonyListenerManager mTelephonyListenerManager;
- @Mock
private InteractionJankMonitor mInteractionJankMonitor;
@Mock
private LatencyTracker mLatencyTracker;
@@ -300,6 +300,8 @@
@Before
public void setup() throws RemoteException {
+ mKosmos = new KosmosJavaAdapter(this);
+ mInteractionJankMonitor = mKosmos.getInteractionJankMonitor();
MockitoAnnotations.initMocks(this);
when(mSessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(mKeyguardInstanceId);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index d742da7..1c77351 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -147,6 +147,7 @@
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR);
+ mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
mFeatureFlags = new FakeFeatureFlags();
mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
index b478d5c..c674294 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.accessibility;
import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
@@ -38,7 +39,7 @@
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -65,7 +66,7 @@
@Mock
private ShadeController mShadeController;
@Mock
- private ShadeViewController mShadeViewController;
+ private PanelExpansionInteractor mPanelExpansionInteractor;
@Mock
private Optional<Recents> mRecentsOptional;
@Mock
@@ -87,7 +88,7 @@
mNotificationShadeController,
mKeyguardStateController,
mShadeController,
- () -> mShadeViewController,
+ () -> mPanelExpansionInteractor,
mRecentsOptional,
mDisplayTracker);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index 64936862..69cd592 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -476,12 +476,18 @@
mValueAnimator.end();
});
- verify(mSpyController).enableWindowMagnificationInternal(
+ // Verify the method is called in
+ // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once and
+ // {@link Animator.AnimatorListener#onAnimationEnd} once in {@link ValueAnimator#end()}
+ verify(mSpyController, times(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
- //Animating in reverse, so we only check if the start values are greater than current.
+
+ // The animation is playing forwards, so we only check the animated values are greater than
+ // the current one. (Asserting the first captured scale)
assertTrue(mScaleCaptor.getAllValues().get(0) > mCurrentScale.get());
+ // The last captured scale equalsto the targetScale when we enable window magnification.
assertEquals(targetScale, mScaleCaptor.getValue(), 0f);
assertTrue(mCenterXCaptor.getAllValues().get(0) > mCurrentCenterX.get());
assertEquals(targetCenterX, mCenterXCaptor.getValue(), 0f);
@@ -588,10 +594,10 @@
final float expectedY = (int) (windowBounds.exactCenterY() + expectedOffset
- defaultMagnificationWindowSize / 2);
- // This is called 4 times when (1) first creating WindowlessMirrorWindow (2) SurfaceView is
+ // This is called 5 times when (1) first creating WindowlessMirrorWindow (2) SurfaceView is
// created and we place the mirrored content as a child of the SurfaceView
- // (3) the animation starts (4) the animation updates
- verify(mTransaction, times(4))
+ // (3) the animation starts (4) the animation updates (5) the animation ends
+ verify(mTransaction, times(5))
.setPosition(any(SurfaceControl.class), eq(expectedX), eq(expectedY));
}
@@ -781,16 +787,18 @@
// wait for animation returns
waitForIdleSync();
- // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)} will only
- // be triggered once in {@link ValueAnimator#end()}
- verify(mSpyController).enableWindowMagnificationInternal(
+ // Verify the method is called in
+ // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once and
+ // {@link Animator.AnimatorListener#onAnimationEnd} once in {@link ValueAnimator#end()}
+ verify(mSpyController, times(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
- //The animation is in verse, so we only check the start values should no be greater than
- // the current one.
+ // The animation is playing backwards, so we only check the animated values should not be
+ // greater than the current one. (Asserting the first captured scale)
assertTrue(mScaleCaptor.getAllValues().get(0) <= mCurrentScale.get());
+ // The last captured scale is 1.0 when we delete window magnification.
assertEquals(1.0f, mScaleCaptor.getValue(), 0f);
verifyStartValue(mCenterXCaptor, Float.NaN);
verifyStartValue(mCenterYCaptor, Float.NaN);
@@ -823,10 +831,15 @@
resetMockObjects();
deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback2);
- verify(mSpyController).enableWindowMagnificationInternal(
+ // Verify the method is called in
+ // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once and
+ // {@link Animator.AnimatorListener#onAnimationEnd} once when running the animation at
+ // the final duration time.
+ verify(mSpyController, times(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
+ // The last captured scale is 1.0 when we delete window magnification.
assertEquals(1.0f, mScaleCaptor.getValue(), 0f);
verifyStartValue(mOffsetXCaptor, 0f);
verifyStartValue(mOffsetYCaptor, 0f);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
index 08b49e7..cbdc696 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
@@ -132,7 +132,8 @@
public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiTestCase {
@Rule
- public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this);
+ // NOTE: pass 'null' to allow this test advances time on the main thread.
+ public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(/* test= */ null);
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index 976cd5bd..1f7d033 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -90,12 +90,12 @@
mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance,
mSecureSettings));
mMenuView.setTranslationY(halfScreenHeight);
- doNothing().when(mMenuView).gotoEditScreen();
mMenuViewLayer = spy(new MenuViewLayer(
mContext, stubWindowManager, mAccessibilityManager,
stubMenuViewModel, stubMenuViewAppearance, mMenuView,
mock(IAccessibilityFloatingMenu.class), mSecureSettings));
+ doNothing().when(mMenuViewLayer).gotoEditScreen();
doReturn(mDraggableBounds).when(mMenuView).getMenuDraggableBounds();
mStubListView = new RecyclerView(mContext);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index de795a7..de696f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -35,6 +35,7 @@
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -49,6 +50,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.graphics.Insets;
@@ -136,6 +138,8 @@
private WindowManager mStubWindowManager;
@Mock
private AccessibilityManager mStubAccessibilityManager;
+ @Mock
+ private PackageManager mMockPackageManager;
private final SecureSettings mSecureSettings = TestUtils.mockSecureSettings();
private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
@@ -162,14 +166,15 @@
new MenuView(mSpyContext, mMenuViewModel, menuViewAppearance, mSecureSettings));
// Ensure tests don't actually update metrics.
doNothing().when(mMenuView).incrementTexMetric(any(), anyInt());
- doNothing().when(mMenuView).gotoEditScreen();
mMenuViewLayer = spy(new MenuViewLayer(mSpyContext, mStubWindowManager,
mStubAccessibilityManager, mMenuViewModel, menuViewAppearance, mMenuView,
mFloatingMenu, mSecureSettings));
- mMenuView = (MenuView) mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW);
mMenuAnimationController = mMenuView.getMenuAnimationController();
+ doNothing().when(mSpyContext).startActivity(any());
+ when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
+
mLastAccessibilityButtonTargets =
Settings.Secure.getStringForUser(mSpyContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
@@ -277,9 +282,31 @@
@Test
@EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
- public void onEditAction_gotoEditScreen_isCalled() {
+ public void onEditAction_startsActivity() {
+ mockActivityQuery(true);
mMenuViewLayer.dispatchAccessibilityAction(R.id.action_edit);
- verify(mMenuView).gotoEditScreen();
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mSpyContext).startActivity(intentCaptor.capture());
+ assertThat(intentCaptor.getValue().getAction()).isEqualTo(
+ mMenuViewLayer.getIntentForEditScreen().getAction());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void onEditAction_noResolve_doesNotStart() {
+ mockActivityQuery(false);
+ mMenuViewLayer.dispatchAccessibilityAction(R.id.action_edit);
+ verify(mSpyContext, never()).startActivity(any());
+ }
+
+ @Test
+ public void getIntentForEditScreen_validate() {
+ Intent intent = mMenuViewLayer.getIntentForEditScreen();
+ String[] targets = intent.getBundleExtra(
+ ":settings:show_fragment_args").getStringArray("targets");
+
+ assertThat(intent.getAction()).isEqualTo(Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS);
+ assertThat(targets).asList().containsExactlyElementsIn(TestUtils.TEST_BUTTON_TARGETS);
}
@Test
@@ -308,20 +335,6 @@
}
@Test
- @DisableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
- public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme_old() {
- mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100));
- final PointF beforePosition = mMenuView.getMenuPosition();
-
- dispatchShowingImeInsets();
-
- final float menuBottom = mMenuView.getTranslationY() + mMenuView.getMenuHeight();
- assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
- assertThat(menuBottom).isLessThan(beforePosition.y);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() {
mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100));
final PointF beforePosition = mMenuView.getMenuPosition();
@@ -337,19 +350,6 @@
}
@Test
- @DisableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
- public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition_old() {
- mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200));
- final PointF beforePosition = mMenuView.getMenuPosition();
-
- dispatchHidingImeInsets();
-
- assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
- assertThat(mMenuView.getTranslationY()).isEqualTo(beforePosition.y);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition() {
mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200));
final PointF beforePosition = mMenuView.getMenuPosition();
@@ -527,4 +527,15 @@
magnetListener.onReleasedInTarget(
new MagnetizedObject.MagneticTarget(view, 200), mock(MagnetizedObject.class));
}
+
+ private void mockActivityQuery(boolean successfulQuery) {
+ // Query just needs to return a non-empty set to be successful.
+ ArrayList<ResolveInfo> resolveInfos = new ArrayList<>();
+ if (successfulQuery) {
+ resolveInfos.add(new ResolveInfo());
+ }
+ when(mMockPackageManager.queryIntentActivities(
+ any(), any(PackageManager.ResolveInfoFlags.class))).thenReturn(resolveInfos);
+ }
+
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index eced465..f6288b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -22,19 +22,13 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.app.UiModeManager;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.platform.test.annotations.EnableFlags;
-import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowManager;
@@ -58,8 +52,6 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import java.util.ArrayList;
-
/** Tests for {@link MenuView}. */
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -79,8 +71,6 @@
private AccessibilityManager mAccessibilityManager;
private SysuiTestableContext mSpyContext;
- @Mock
- private PackageManager mMockPackageManager;
@Before
public void setUp() throws Exception {
@@ -91,7 +81,6 @@
mSpyContext = spy(mContext);
doNothing().when(mSpyContext).startActivity(any());
- when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
final SecureSettings secureSettings = TestUtils.mockSecureSettings();
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
secureSettings);
@@ -178,32 +167,6 @@
assertThat(radiiAnimator.isStarted()).isTrue();
}
- @Test
- public void getIntentForEditScreen_validate() {
- Intent intent = mMenuView.getIntentForEditScreen();
- String[] targets = intent.getBundleExtra(
- ":settings:show_fragment_args").getStringArray("targets");
-
- assertThat(intent.getAction()).isEqualTo(Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS);
- assertThat(targets).asList().containsExactlyElementsIn(TestUtils.TEST_BUTTON_TARGETS);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
- public void gotoEditScreen_sendsIntent() {
- mockActivityQuery(true);
- mMenuView.gotoEditScreen();
- verify(mSpyContext).startActivity(any());
- }
-
- @Test
- @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
- public void gotoEditScreen_noResolve_doesNotStart() {
- mockActivityQuery(false);
- mMenuView.gotoEditScreen();
- verify(mSpyContext, never()).startActivity(any());
- }
-
private InstantInsetLayerDrawable getMenuViewInsetLayer() {
return (InstantInsetLayerDrawable) mMenuView.getBackground();
}
@@ -226,14 +189,4 @@
mUiModeManager.setNightMode(mNightMode);
Prefs.putString(mContext, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, mLastPosition);
}
-
- private void mockActivityQuery(boolean successfulQuery) {
- // Query just needs to return a non-empty set to be successful.
- ArrayList<ResolveInfo> resolveInfos = new ArrayList<>();
- if (successfulQuery) {
- resolveInfos.add(new ResolveInfo());
- }
- when(mMockPackageManager.queryIntentActivities(
- any(), any(PackageManager.ResolveInfoFlags.class))).thenReturn(resolveInfos);
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt
index 96ce3ab..b73e4e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt
@@ -18,6 +18,8 @@
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.policy.DecorView
import com.android.systemui.SysuiTestCase
+import com.android.systemui.jank.interactionJankMonitor
+import com.android.systemui.kosmos.Kosmos
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
@@ -31,7 +33,6 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
@@ -43,7 +44,7 @@
private lateinit var mDialogTransitionAnimator: DialogTransitionAnimator
private val attachedViews = mutableSetOf<View>()
- @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
+ val interactionJankMonitor = Kosmos().interactionJankMonitor
@get:Rule val rule = MockitoJUnit.rule()
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 2b4e9ec..072569d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -39,6 +39,7 @@
import androidx.test.filters.SmallTest
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.Flags.FLAG_CONSTRAINT_BP
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
@@ -375,6 +376,7 @@
@Test
fun testShowBiometricUI() {
+ mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP)
val container = initializeFingerprintContainer()
waitForIdleSync()
@@ -397,6 +399,7 @@
@Test
@Ignore("b/302735104")
fun testShowCredentialUI_withCustomBp() {
+ mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP)
val container = initializeFingerprintContainer(
authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
isUsingContentView = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 1aab9e8..7db4ca9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -1338,6 +1338,17 @@
assertThat(logoDescription).isEqualTo(logoDescriptionFromApp)
}
+ @Test
+ fun iconViewLoaded() = runGenericTest {
+ val isIconViewLoaded by collectLastValue(viewModel.isIconViewLoaded)
+ // TODO(b/328677869): Add test for noIcon logic.
+ assertThat(isIconViewLoaded).isFalse()
+
+ viewModel.setIsIconViewLoaded(true)
+
+ assertThat(isIconViewLoaded).isTrue()
+ }
+
/** Asserts that the selected buttons are visible now. */
private suspend fun TestScope.assertButtonsVisible(
tryAgain: Boolean = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index 3f13033..45d20dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -188,6 +188,20 @@
}
@Test
+ public void testRegisterSensor_OccludingActivity() {
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
+
+ ArgumentCaptor<StatusBarStateController.StateListener> stateListenerArgumentCaptor =
+ ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
+ verify(mStatusBarStateController).addCallback(stateListenerArgumentCaptor.capture());
+
+ mFalsingCollector.onScreenTurningOn();
+ reset(mProximitySensor);
+ stateListenerArgumentCaptor.getValue().onStateChanged(StatusBarState.SHADE);
+ verify(mProximitySensor).register(any(ThresholdSensor.Listener.class));
+ }
+
+ @Test
public void testPassThroughGesture() {
MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt
index 21b8aca..c79cbab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt
@@ -85,7 +85,9 @@
testScope.runTest {
val state = displayState()
- deviceStateManagerListener.value.onStateChanged(TEST_FOLDED)
+ deviceStateManagerListener.value.onDeviceStateChanged(
+ getDeviceStateForIdentifier(TEST_FOLDED)
+ )
assertThat(state()).isEqualTo(DeviceState.FOLDED)
}
@@ -95,7 +97,9 @@
testScope.runTest {
val state = displayState()
- deviceStateManagerListener.value.onStateChanged(TEST_HALF_FOLDED)
+ deviceStateManagerListener.value.onDeviceStateChanged(
+ getDeviceStateForIdentifier(TEST_HALF_FOLDED)
+ )
assertThat(state()).isEqualTo(DeviceState.HALF_FOLDED)
}
@@ -105,7 +109,9 @@
testScope.runTest {
val state = displayState()
- deviceStateManagerListener.value.onStateChanged(TEST_UNFOLDED)
+ deviceStateManagerListener.value.onDeviceStateChanged(
+ getDeviceStateForIdentifier(TEST_UNFOLDED)
+ )
assertThat(state()).isEqualTo(DeviceState.UNFOLDED)
}
@@ -115,7 +121,9 @@
testScope.runTest {
val state = displayState()
- deviceStateManagerListener.value.onStateChanged(TEST_REAR_DISPLAY)
+ deviceStateManagerListener.value.onDeviceStateChanged(
+ getDeviceStateForIdentifier(TEST_REAR_DISPLAY)
+ )
assertThat(state()).isEqualTo(DeviceState.REAR_DISPLAY)
}
@@ -125,7 +133,9 @@
testScope.runTest {
val state = displayState()
- deviceStateManagerListener.value.onStateChanged(TEST_CONCURRENT_DISPLAY)
+ deviceStateManagerListener.value.onDeviceStateChanged(
+ getDeviceStateForIdentifier(TEST_CONCURRENT_DISPLAY)
+ )
assertThat(state()).isEqualTo(DeviceState.CONCURRENT_DISPLAY)
}
@@ -135,7 +145,9 @@
testScope.runTest {
val state = displayState()
- deviceStateManagerListener.value.onStateChanged(123456)
+ deviceStateManagerListener.value.onDeviceStateChanged(
+ getDeviceStateForIdentifier(123456)
+ )
assertThat(state()).isEqualTo(DeviceState.UNKNOWN)
}
@@ -152,6 +164,13 @@
private fun Int.toIntArray() = listOf(this).toIntArray()
+ private fun getDeviceStateForIdentifier(id: Int): android.hardware.devicestate.DeviceState {
+ return android.hardware.devicestate.DeviceState(
+ android.hardware.devicestate.DeviceState.Configuration.Builder(id, /* name= */ "")
+ .build()
+ )
+ }
+
private companion object {
// Used to fake the ids in the test. Note that there is no guarantees different devices will
// have the same ids (that's why the ones in this test start from 41)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 1849245..272b488 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -75,7 +75,6 @@
import androidx.test.filters.SmallTest;
import com.android.internal.foldables.FoldGracePeriodProvider;
-import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.widget.LockPatternUtils;
@@ -181,7 +180,6 @@
private @Mock NotificationShadeDepthController mNotificationShadeDepthController;
private @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private @Mock ScreenOffAnimationController mScreenOffAnimationController;
- private @Mock InteractionJankMonitor mInteractionJankMonitor;
private @Mock ScreenOnCoordinator mScreenOnCoordinator;
private @Mock KeyguardTransitions mKeyguardTransitions;
private @Mock ShadeController mShadeController;
@@ -235,8 +233,6 @@
when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
when(mPowerManager.newWakeLock(anyInt(), any())).thenReturn(mock(WakeLock.class));
when(mPowerManager.isInteractive()).thenReturn(true);
- when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true);
- when(mInteractionJankMonitor.end(anyInt())).thenReturn(true);
mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager);
final ViewRootImpl testViewRoot = mock(ViewRootImpl.class);
when(testViewRoot.getView()).thenReturn(mock(View.class));
@@ -1245,7 +1241,7 @@
() -> mNotificationShadeDepthController,
mScreenOnCoordinator,
mKeyguardTransitions,
- mInteractionJankMonitor,
+ mKosmos.getInteractionJankMonitor(),
mDreamOverlayStateController,
mJavaAdapter,
mWallpaperRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index ad80a06..8700001 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -27,7 +27,9 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -37,6 +39,7 @@
import java.math.BigDecimal
import java.math.RoundingMode
import java.util.UUID
+import kotlinx.coroutines.flow.dropWhile
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.TestScope
@@ -224,6 +227,101 @@
}
@Test
+ fun startingSecondManualTransitionWillCancelPreviousManualTransition() =
+ TestScope().runTest {
+ // Drop initial steps from OFF which are sent in the constructor
+ val steps = mutableListOf<TransitionStep>()
+ val job =
+ underTest.transitions
+ .dropWhile { step -> step.from == OFF }
+ .onEach { steps.add(it) }
+ .launchIn(this)
+
+ val firstUuid =
+ underTest.startTransition(
+ TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator = null)
+ )
+ runCurrent()
+
+ checkNotNull(firstUuid)
+ underTest.updateTransition(firstUuid, 0.5f, TransitionState.RUNNING)
+ runCurrent()
+
+ val secondUuid =
+ underTest.startTransition(
+ TransitionInfo(OWNER_NAME, AOD, DREAMING, animator = null)
+ )
+ runCurrent()
+
+ checkNotNull(secondUuid)
+ underTest.updateTransition(secondUuid, 0.7f, TransitionState.RUNNING)
+ // Trying to transition the old uuid should be ignored.
+ underTest.updateTransition(firstUuid, 0.6f, TransitionState.RUNNING)
+ runCurrent()
+
+ assertThat(steps)
+ .containsExactly(
+ TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME),
+ TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING, OWNER_NAME),
+ TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.CANCELED, OWNER_NAME),
+ TransitionStep(AOD, DREAMING, 0.5f, TransitionState.STARTED, OWNER_NAME),
+ TransitionStep(AOD, DREAMING, 0.7f, TransitionState.RUNNING, OWNER_NAME),
+ )
+ .inOrder()
+
+ job.cancel()
+ }
+
+ @Test
+ fun startingSecondTransitionWillCancelPreviousManualTransition() =
+ TestScope().runTest {
+ // Drop initial steps from OFF which are sent in the constructor
+ val steps = mutableListOf<TransitionStep>()
+ val job =
+ underTest.transitions
+ .dropWhile { step -> step.from == OFF }
+ .onEach { steps.add(it) }
+ .launchIn(this)
+
+ val uuid =
+ underTest.startTransition(
+ TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator = null)
+ )
+ runCurrent()
+
+ checkNotNull(uuid)
+ underTest.updateTransition(uuid, 0.5f, TransitionState.RUNNING)
+ runCurrent()
+
+ // Start new transition to dreaming, should cancel previous one.
+ runner.startTransition(
+ this,
+ TransitionInfo(
+ OWNER_NAME,
+ AOD,
+ DREAMING,
+ getAnimator(),
+ TransitionModeOnCanceled.RESET,
+ ),
+ )
+ runCurrent()
+
+ // Trying to transition the old uuid should be ignored.
+ underTest.updateTransition(uuid, 0.6f, TransitionState.RUNNING)
+ runCurrent()
+
+ assertThat(steps.take(3))
+ .containsExactly(
+ TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME),
+ TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING, OWNER_NAME),
+ TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.CANCELED, OWNER_NAME),
+ )
+ .inOrder()
+
+ job.cancel()
+ }
+
+ @Test
fun attemptTomanuallyUpdateTransitionWithInvalidUUIDthrowsException() {
underTest.updateTransition(UUID.randomUUID(), 0f, TransitionState.RUNNING)
assertThat(wtfHandler.failed).isTrue()
@@ -336,6 +434,7 @@
private class WtfHandler : TerribleFailureHandler {
var failed = false
+
override fun onTerribleFailure(tag: String, what: TerribleFailure, system: Boolean) {
failed = true
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
index 7ee8963..1839d8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
@@ -37,8 +37,6 @@
import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.selectedUserInteractor
import junit.framework.Assert.assertEquals
-import junit.framework.Assert.assertTrue
-import junit.framework.Assert.fail
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runCurrent
@@ -144,73 +142,6 @@
}
@Test
- fun testSurfaceBehindModel() =
- testScope.runTest {
- val values by collectValues(underTest.surfaceBehindModel)
-
- transitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
- from = KeyguardState.PRIMARY_BOUNCER,
- to = KeyguardState.LOCKSCREEN,
- )
- )
- runCurrent()
-
- assertEquals(
- listOf(
- null, // PRIMARY_BOUNCER -> LOCKSCREEN does not have specific view params.
- ),
- values
- )
-
- transitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
- from = KeyguardState.PRIMARY_BOUNCER,
- to = KeyguardState.GONE,
- )
- )
- runCurrent()
-
- transitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.RUNNING,
- from = KeyguardState.PRIMARY_BOUNCER,
- to = KeyguardState.GONE,
- value = 0.01f,
- )
- )
- runCurrent()
-
- transitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.RUNNING,
- from = KeyguardState.PRIMARY_BOUNCER,
- to = KeyguardState.GONE,
- value = 0.99f,
- )
- )
- runCurrent()
-
- assertEquals(3, values.size)
- val model1percent = values[1]
- val model99percent = values[2]
-
- try {
- // We should initially have an alpha of 0f when unlocking, so the surface is not
- // visible
- // while lockscreen UI animates out.
- assertEquals(0f, model1percent!!.alpha)
-
- // By the end it should probably be visible.
- assertTrue(model99percent!!.alpha > 0f)
- } catch (e: NullPointerException) {
- fail("surfaceBehindModel was unexpectedly null.")
- }
- }
-
- @Test
@EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testReturnToLockscreen_whenBouncerHides() =
testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index 09c56b0..58273d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -107,6 +107,7 @@
@Test
fun addViewsConditionally_migrateFlagOff() {
+ mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
mSetFlagsRule.disableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
val constraintLayout = ConstraintLayout(context, null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
index 87391cc..d410dac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -36,6 +37,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import org.mockito.Mockito.verify
@ExperimentalCoroutinesApi
@RunWith(JUnit4::class)
@@ -48,6 +50,20 @@
private val underTest = kosmos.alternateBouncerViewModel
@Test
+ fun showPrimaryBouncer() =
+ testScope.runTest {
+ underTest.showPrimaryBouncer()
+ verify(statusBarKeyguardViewManager).showPrimaryBouncer(any())
+ }
+
+ @Test
+ fun hideAlternateBouncer() =
+ testScope.runTest {
+ underTest.hideAlternateBouncer()
+ verify(statusBarKeyguardViewManager).hideAlternateBouncer(any())
+ }
+
+ @Test
fun transitionToAlternateBouncer_scrimAlphaUpdate() =
testScope.runTest {
val scrimAlphas by collectValues(underTest.scrimAlpha)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index d75553f..b593def 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -81,7 +81,7 @@
@Test
fun loadRecentTasks_singleTask_returnsTaskAsNotForeground() {
givenRecentTasks(
- createSingleTask(taskId = 1),
+ createSingleTask(taskId = 1, isVisible = true),
)
val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
@@ -90,10 +90,10 @@
}
@Test
- fun loadRecentTasks_multipleTasks_returnsSecondTaskAsForegroundTask() {
+ fun loadRecentTasks_multipleTasks_returnsSecondVisibleTaskAsForegroundTask() {
givenRecentTasks(
createSingleTask(taskId = 1),
- createSingleTask(taskId = 2),
+ createSingleTask(taskId = 2, isVisible = true),
createSingleTask(taskId = 3),
)
@@ -103,10 +103,25 @@
}
@Test
- fun loadRecentTasks_secondTaskIsGrouped_marksBothGroupedTasksAsForeground() {
+ fun loadRecentTasks_multipleTasks_returnsSecondInvisibleTaskAsNotForegroundTask() {
givenRecentTasks(
createSingleTask(taskId = 1),
- createTaskPair(taskId1 = 2, taskId2 = 3),
+ createSingleTask(taskId = 2, isVisible = false),
+ createSingleTask(taskId = 3),
+ )
+
+ val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+ assertThat(result.map { it.isForegroundTask })
+ .containsExactly(false, false, false)
+ .inOrder()
+ }
+
+ @Test
+ fun loadRecentTasks_secondTaskIsGroupedAndVisible_marksBothGroupedTasksAsForeground() {
+ givenRecentTasks(
+ createSingleTask(taskId = 1),
+ createTaskPair(taskId1 = 2, taskId2 = 3, isVisible = true),
createSingleTask(taskId = 4),
)
@@ -117,6 +132,21 @@
.inOrder()
}
+ @Test
+ fun loadRecentTasks_secondTaskIsGroupedAndInvisible_marksBothGroupedTasksAsNotForeground() {
+ givenRecentTasks(
+ createSingleTask(taskId = 1),
+ createTaskPair(taskId1 = 2, taskId2 = 3, isVisible = false),
+ createSingleTask(taskId = 4),
+ )
+
+ val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+ assertThat(result.map { it.isForegroundTask })
+ .containsExactly(false, false, false, false)
+ .inOrder()
+ }
+
@Suppress("UNCHECKED_CAST")
private fun givenRecentTasks(vararg tasks: GroupedRecentTaskInfo) {
whenever(recentTasks.getRecentTasks(any(), any(), any(), any(), any())).thenAnswer {
@@ -136,11 +166,23 @@
isForegroundTask = false,
)
- private fun createSingleTask(taskId: Int): GroupedRecentTaskInfo =
- GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId))
+ private fun createSingleTask(taskId: Int, isVisible: Boolean = false): GroupedRecentTaskInfo =
+ GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId, isVisible))
- private fun createTaskPair(taskId1: Int, taskId2: Int): GroupedRecentTaskInfo =
- GroupedRecentTaskInfo.forSplitTasks(createTaskInfo(taskId1), createTaskInfo(taskId2), null)
+ private fun createTaskPair(
+ taskId1: Int,
+ taskId2: Int,
+ isVisible: Boolean = false
+ ): GroupedRecentTaskInfo =
+ GroupedRecentTaskInfo.forSplitTasks(
+ createTaskInfo(taskId1, isVisible),
+ createTaskInfo(taskId2, isVisible),
+ null
+ )
- private fun createTaskInfo(taskId: Int) = RecentTaskInfo().apply { this.taskId = taskId }
+ private fun createTaskInfo(taskId: Int, isVisible: Boolean = false) =
+ RecentTaskInfo().apply {
+ this.taskId = taskId
+ this.isVisible = isVisible
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 31746a2..b38d5e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -99,6 +99,7 @@
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.CommandQueue;
@@ -580,6 +581,7 @@
() -> Optional.of(mCentralSurfaces),
mKeyguardStateController,
mock(ShadeViewController.class),
+ mock(PanelExpansionInteractor.class),
mock(NotificationRemoteInputManager.class),
mock(NotificationShadeDepthController.class),
mHandler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
index 3122edb..761c411 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
import com.android.systemui.recordissue.RecordIssueDialogDelegate
import com.android.systemui.res.R
import com.android.systemui.settings.UserContextProvider
@@ -66,6 +67,7 @@
@Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var dialogLauncherAnimator: DialogTransitionAnimator
+ @Mock private lateinit var panelInteractor: PanelInteractor
@Mock private lateinit var userContextProvider: UserContextProvider
@Mock private lateinit var delegateFactory: RecordIssueDialogDelegate.Factory
@Mock private lateinit var dialogDelegate: RecordIssueDialogDelegate
@@ -96,6 +98,7 @@
keyguardDismissUtil,
keyguardStateController,
dialogLauncherAnimator,
+ panelInteractor,
userContextProvider,
delegateFactory,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt
index 8986d99..cd1452a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt
@@ -68,7 +68,7 @@
fun testGetValue_valueUnset() {
testScope.runTest {
userRepository.setSelectedUserInfo(SYSTEM_USER)
- val actualValue by collectLastValue(bluetoothAutoOnRepository.getValue)
+ val actualValue by collectLastValue(bluetoothAutoOnRepository.isAutoOn)
runCurrent()
@@ -81,7 +81,7 @@
fun testGetValue_valueFalse() {
testScope.runTest {
userRepository.setSelectedUserInfo(SYSTEM_USER)
- val actualValue by collectLastValue(bluetoothAutoOnRepository.getValue)
+ val actualValue by collectLastValue(bluetoothAutoOnRepository.isAutoOn)
secureSettings.putIntForUser(SETTING_NAME, DISABLED, UserHandle.USER_SYSTEM)
runCurrent()
@@ -94,7 +94,7 @@
fun testGetValue_valueTrue() {
testScope.runTest {
userRepository.setSelectedUserInfo(SYSTEM_USER)
- val actualValue by collectLastValue(bluetoothAutoOnRepository.getValue)
+ val actualValue by collectLastValue(bluetoothAutoOnRepository.isAutoOn)
secureSettings.putIntForUser(SETTING_NAME, ENABLED, UserHandle.USER_SYSTEM)
runCurrent()
@@ -104,15 +104,16 @@
}
@Test
- fun testGetValue_valueTrue_secondaryUser_returnUnset() {
+ fun testGetValue_valueTrue_secondaryUser_returnTrue() {
testScope.runTest {
userRepository.setSelectedUserInfo(SECONDARY_USER)
- val actualValue by collectLastValue(bluetoothAutoOnRepository.getValue)
+ val actualValue by collectLastValue(bluetoothAutoOnRepository.isAutoOn)
+ secureSettings.putIntForUser(SETTING_NAME, DISABLED, SYSTEM_USER_ID)
secureSettings.putIntForUser(SETTING_NAME, ENABLED, SECONDARY_USER_ID)
runCurrent()
- assertThat(actualValue).isEqualTo(UNSET)
+ assertThat(actualValue).isEqualTo(ENABLED)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
index dc211303..f88a5a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
@@ -19,7 +19,6 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotSame;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.reset;
@@ -28,6 +27,7 @@
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -38,9 +38,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.res.R;
import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.model.SysUiState;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.CommandQueue;
@@ -176,6 +174,6 @@
DeviceStateManager.DeviceStateCallback {
@Override
- public void onStateChanged(int state) { }
+ public void onDeviceStateChanged(DeviceState state) { }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java
deleted file mode 100644
index 9ea30d6..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2020 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.screenshot;
-
-import static com.android.systemui.screenshot.ScreenshotController.ACTION_TYPE_SHARE;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED;
-import static com.android.systemui.statusbar.phone.CentralSurfaces.SYSTEM_DIALOG_REASON_SCREENSHOT;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import android.app.PendingIntent;
-import android.content.Intent;
-import android.os.Bundle;
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.settings.FakeDisplayTracker;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.stubbing.Answer;
-
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
-
-@RunWith(AndroidTestingRunner.class)
-@SmallTest
-public class ActionProxyReceiverTest extends SysuiTestCase {
- @Mock
- private ActivityManagerWrapper mMockActivityManagerWrapper;
- @Mock
- private ScreenshotSmartActions mMockScreenshotSmartActions;
- @Mock
- private PendingIntent mMockPendingIntent;
- @Mock
- private ActivityStarter mActivityStarter;
-
- private Intent mIntent;
- private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
-
- @Before
- public void setup() throws InterruptedException, ExecutionException, TimeoutException {
- MockitoAnnotations.initMocks(this);
- mIntent = new Intent(mContext, ActionProxyReceiver.class)
- .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, mMockPendingIntent);
- }
-
- @Test
- public void testPendingIntentSentWithStatusBar() throws PendingIntent.CanceledException {
- ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver();
- // ensure that the pending intent call is passed through
- doAnswer((Answer<Object>) invocation -> {
- ((Runnable) invocation.getArgument(0)).run();
- return null;
- }).when(mActivityStarter).executeRunnableDismissingKeyguard(
- any(Runnable.class), isNull(), anyBoolean(), anyBoolean(), anyBoolean());
-
- actionProxyReceiver.onReceive(mContext, mIntent);
-
- verify(mMockActivityManagerWrapper).closeSystemWindows(SYSTEM_DIALOG_REASON_SCREENSHOT);
- verify(mActivityStarter).executeRunnableDismissingKeyguard(
- any(Runnable.class), isNull(), eq(true), eq(true), eq(true));
- verify(mMockPendingIntent).send(
- eq(mContext), anyInt(), isNull(), isNull(), isNull(), isNull(), any(Bundle.class));
- }
-
- @Test
- public void testSmartActionsNotNotifiedByDefault() {
- ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver();
-
- actionProxyReceiver.onReceive(mContext, mIntent);
-
- verify(mMockScreenshotSmartActions, never())
- .notifyScreenshotAction(anyString(), anyString(), anyBoolean(),
- any(Intent.class));
- }
-
- @Test
- public void testSmartActionsNotifiedIfEnabled() {
- ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver();
- mIntent.putExtra(EXTRA_SMART_ACTIONS_ENABLED, true);
- String testId = "testID";
- mIntent.putExtra(EXTRA_ID, testId);
-
- actionProxyReceiver.onReceive(mContext, mIntent);
-
- verify(mMockScreenshotSmartActions).notifyScreenshotAction(
- testId, ACTION_TYPE_SHARE, false, null);
- }
-
- private ActionProxyReceiver constructActionProxyReceiver() {
- return new ActionProxyReceiver(
- mMockActivityManagerWrapper,
- mMockScreenshotSmartActions,
- mDisplayTracker,
- mActivityStarter
- );
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java
deleted file mode 100644
index d58f47a..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2020 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.screenshot;
-
-import static com.android.systemui.screenshot.ScreenshotController.ACTION_TYPE_DELETE;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED;
-import static com.android.systemui.screenshot.ScreenshotController.SCREENSHOT_URI_ID;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Intent;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Environment;
-import android.provider.MediaStore;
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.File;
-import java.util.concurrent.Executor;
-
-@RunWith(AndroidTestingRunner.class)
-@SmallTest
-public class DeleteScreenshotReceiverTest extends SysuiTestCase {
-
- @Mock
- private ScreenshotSmartActions mMockScreenshotSmartActions;
- @Mock
- private Executor mMockExecutor;
-
- private DeleteScreenshotReceiver mDeleteScreenshotReceiver;
- private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- mDeleteScreenshotReceiver =
- new DeleteScreenshotReceiver(mMockScreenshotSmartActions, mMockExecutor);
- }
-
- @Test
- public void testNoUriProvided() {
- Intent intent = new Intent(mContext, DeleteScreenshotReceiver.class);
-
- mDeleteScreenshotReceiver.onReceive(mContext, intent);
-
- verify(mMockExecutor, never()).execute(any(Runnable.class));
- verify(mMockScreenshotSmartActions, never()).notifyScreenshotAction(
- any(String.class), any(String.class), anyBoolean(),
- any(Intent.class));
- }
-
- @Test
- public void testFileDeleted() {
- DeleteScreenshotReceiver deleteScreenshotReceiver =
- new DeleteScreenshotReceiver(mMockScreenshotSmartActions, mFakeExecutor);
- ContentResolver contentResolver = mContext.getContentResolver();
- final Uri testUri = contentResolver.insert(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI, getFakeContentValues());
- assertNotNull(testUri);
-
- try {
- Cursor cursor =
- contentResolver.query(testUri, null, null, null, null);
- assertEquals(1, cursor.getCount());
- Intent intent = new Intent(mContext, DeleteScreenshotReceiver.class)
- .putExtra(SCREENSHOT_URI_ID, testUri.toString());
-
- deleteScreenshotReceiver.onReceive(mContext, intent);
- int runCount = mFakeExecutor.runAllReady();
-
- assertEquals(1, runCount);
- cursor =
- contentResolver.query(testUri, null, null, null, null);
- assertEquals(0, cursor.getCount());
- } finally {
- contentResolver.delete(testUri, null, null);
- }
-
- // ensure smart actions not called by default
- verify(mMockScreenshotSmartActions, never()).notifyScreenshotAction(
- any(String.class), any(String.class), anyBoolean(), any(Intent.class));
- }
-
- @Test
- public void testNotifyScreenshotAction() {
- Intent intent = new Intent(mContext, DeleteScreenshotReceiver.class);
- String uriString = "testUri";
- String testId = "testID";
- intent.putExtra(SCREENSHOT_URI_ID, uriString);
- intent.putExtra(EXTRA_ID, testId);
- intent.putExtra(EXTRA_SMART_ACTIONS_ENABLED, true);
-
- mDeleteScreenshotReceiver.onReceive(mContext, intent);
-
- verify(mMockExecutor).execute(any(Runnable.class));
- verify(mMockScreenshotSmartActions).notifyScreenshotAction(testId,
- ACTION_TYPE_DELETE, false, null);
- }
-
- private static ContentValues getFakeContentValues() {
- final ContentValues values = new ContentValues();
- values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES
- + File.separator + Environment.DIRECTORY_SCREENSHOTS);
- values.put(MediaStore.MediaColumns.DISPLAY_NAME, "test_screenshot");
- values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png");
- values.put(MediaStore.MediaColumns.DATE_ADDED, 0);
- values.put(MediaStore.MediaColumns.DATE_MODIFIED, 0);
- return values;
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeSessionTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/FakeSessionTest.java
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeSessionTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/FakeSessionTest.java
index 4c8a4b0..aad46139 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeSessionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/FakeSessionTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
import static com.google.common.util.concurrent.Futures.getUnchecked;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureClientTest.java
similarity index 93%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureClientTest.java
index 670a130..1023260 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureClientTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
import static org.junit.Assert.assertEquals;
@@ -37,8 +37,8 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult;
-import com.android.systemui.screenshot.ScrollCaptureClient.Session;
+import com.android.systemui.screenshot.scroll.ScrollCaptureClient.CaptureResult;
+import com.android.systemui.screenshot.scroll.ScrollCaptureClient.Session;
import com.google.common.util.concurrent.ListenableFuture;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureControllerTest.java
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureControllerTest.java
index 6f081c7..f39f543 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureControllerTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
import static com.google.common.util.concurrent.Futures.getUnchecked;
import static com.google.common.util.concurrent.Futures.immediateFuture;
@@ -36,7 +36,7 @@
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.screenshot.ScrollCaptureClient.Session;
+import com.android.systemui.screenshot.scroll.ScrollCaptureClient.Session;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureFrameworkSmokeTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureFrameworkSmokeTest.java
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureFrameworkSmokeTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureFrameworkSmokeTest.java
index de97bc3..5699cfc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureFrameworkSmokeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureFrameworkSmokeTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollViewActivity.java
similarity index 93%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java
rename to packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollViewActivity.java
index 4c84df2..04aba11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollViewActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
import android.app.Activity;
import android.os.Bundle;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index c31c625..1ee26db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -196,7 +196,8 @@
new ConfigurationInteractor(configurationRepository),
shadeRepository,
keyguardTransitionInteractor,
- () -> sceneInteractor);
+ () -> sceneInteractor,
+ () -> mKosmos.getFromGoneTransitionInteractor());
CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 617b25d..88b239a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -481,8 +481,7 @@
// AND status bar doesn't want it
whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT))
.thenReturn(false)
- // AND shade is not fully expanded
- whenever(notificationPanelViewController.isFullyExpanded()).thenReturn(false)
+ // AND shade is not fully expanded (mock is false by default)
// AND the lock icon does NOT want the touch
whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(false)
// AND quick settings controller DOES want it
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index a077164..b9451ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -17,7 +17,6 @@
package com.android.systemui.shade;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -223,7 +222,8 @@
new ConfigurationInteractor(configurationRepository),
mShadeRepository,
keyguardTransitionInteractor,
- () -> sceneInteractor);
+ () -> sceneInteractor,
+ () -> mKosmos.getFromGoneTransitionInteractor());
mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
mFromPrimaryBouncerTransitionInteractor =
@@ -289,10 +289,6 @@
when(mNotificationRemoteInputManager.isRemoteInputActive())
.thenReturn(false);
- when(mInteractionJankMonitor.begin(any(), anyInt()))
- .thenReturn(true);
- when(mInteractionJankMonitor.end(anyInt()))
- .thenReturn(true);
when(mPanelView.getParent()).thenReturn(mPanelViewParent);
when(mQs.getHeader()).thenReturn(mQsHeader);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
index 3defee9..722387c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
@@ -28,7 +28,7 @@
import android.content.pm.ApplicationInfo;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
-
+import androidx.test.filters.FlakyTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
@@ -50,6 +50,7 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
+@FlakyTest(bugId = 327655994) // Also b/324682425
@SmallTest
@RunWith(AndroidJUnit4.class)
public class PluginInstanceTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 103dcb7..dcd000a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -45,6 +45,7 @@
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.fromGoneTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
@@ -153,6 +154,7 @@
shadeRepository,
keyguardTransitionInteractor,
{ kosmos.sceneInteractor },
+ { kosmos.fromGoneTransitionInteractor },
)
whenever(deviceEntryUdfpsInteractor.isUdfpsSupported).thenReturn(MutableStateFlow(false))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index f89b9cd..66a306e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -53,8 +53,6 @@
import com.android.internal.logging.UiEventLogger.UiEventEnum
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.broadcast.FakeBroadcastDispatcher
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogcatEchoTracker
import com.android.systemui.log.core.LogLevel
@@ -150,7 +148,10 @@
@Before
fun setUp() {
- val user = UserInfo(ActivityManager.getCurrentUser(), "Current user", /* flags = */ 0)
+ val userId = ActivityManager.getCurrentUser()
+ val user = UserInfo(userId, "Current user", /* flags = */ 0)
+
+ deviceProvisionedController.currentUser = userId
userTracker.set(listOf(user), /* currentUserIndex = */ 0)
provider.start()
@@ -823,6 +824,13 @@
}
@Test
+ fun testShouldFsi_userSetupIncomplete() {
+ ensureUserSetupIncompleteFsiState()
+ assertShouldFsi(buildFsiEntry())
+ assertNoEventsLogged()
+ }
+
+ @Test
fun testShouldNotFsi_noHunOrKeyguard() {
ensureNoHunOrKeyguardFsiState()
assertShouldNotFsi(buildFsiEntry())
@@ -888,7 +896,8 @@
var statusBarState: Int? = null,
var keyguardIsShowing: Boolean = false,
var keyguardIsOccluded: Boolean = false,
- var deviceProvisioned: Boolean = true
+ var deviceProvisioned: Boolean = true,
+ var currentUserSetup: Boolean = true
)
protected fun setState(state: State): Unit =
@@ -925,6 +934,7 @@
keyguardStateController.isShowing = keyguardIsShowing
deviceProvisionedController.deviceProvisioned = deviceProvisioned
+ deviceProvisionedController.isCurrentUserSetup = currentUserSetup
}
protected fun ensureState(block: State.() -> Unit) =
@@ -999,6 +1009,18 @@
hunSettingEnabled = false
keyguardIsShowing = false
deviceProvisioned = false
+ currentUserSetup = true
+ run(block)
+ }
+
+ protected fun ensureUserSetupIncompleteFsiState(block: State.() -> Unit = {}) = ensureState {
+ isInteractive = true
+ isDreaming = false
+ statusBarState = SHADE
+ hunSettingEnabled = false
+ keyguardIsShowing = false
+ deviceProvisioned = true
+ currentUserSetup = false
run(block)
}
@@ -1009,6 +1031,7 @@
hunSettingEnabled = false
keyguardIsShowing = false
deviceProvisioned = true
+ currentUserSetup = true
run(block)
}
@@ -1216,7 +1239,7 @@
neb.setImportance(importance)
val channel =
- NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance)
+ NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance)
channel.isImportantConversation = isImportantConversation
neb.setChannel(channel)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index ea4ae17..d5c4053 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -49,8 +49,8 @@
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -78,6 +78,7 @@
@Mock private CommandQueue mCommandQueue;
@Mock private QuickSettingsController mQuickSettingsController;
@Mock private ShadeViewController mShadeViewController;
+ @Mock private PanelExpansionInteractor mPanelExpansionInteractor;
@Mock private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
private final MetricsLogger mMetricsLogger = new FakeMetricsLogger();
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -112,6 +113,7 @@
mShadeController,
mCommandQueue,
mShadeViewController,
+ mPanelExpansionInteractor,
mRemoteInputQuickSettingsDisabler,
mMetricsLogger,
mKeyguardUpdateMonitor,
@@ -126,7 +128,6 @@
mStatusBarHideIconsForBouncerManager,
mPowerManager,
Optional.of(mVibrator),
- new DisableFlagsLogger(),
DEFAULT_DISPLAY,
mCameraLauncherLazy,
mUserTracker,
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 c8c54dbd..611cf91 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
@@ -49,6 +49,7 @@
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.IntentFilter;
+import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.AmbientDisplayConfiguration;
import android.hardware.fingerprint.FingerprintManager;
@@ -1113,10 +1114,12 @@
}
private void setDeviceState(int state) {
+ DeviceState deviceState = new DeviceState(
+ new DeviceState.Configuration.Builder(state, "TEST").build());
ArgumentCaptor<DeviceStateManager.DeviceStateCallback> callbackCaptor =
ArgumentCaptor.forClass(DeviceStateManager.DeviceStateCallback.class);
verify(mDeviceStateManager).registerCallback(any(), callbackCaptor.capture());
- callbackCaptor.getValue().onStateChanged(state);
+ callbackCaptor.getValue().onDeviceStateChanged(deviceState);
}
private void setGoToSleepStates(int... states) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt
index 649dc23..5d42d51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.statusbar.phone
+import android.hardware.devicestate.DeviceState
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.internal.R
@@ -40,52 +41,52 @@
@Before
fun setUp() {
initMocks(this)
- setFoldedStates(DEVICE_STATE_FOLDED)
- setGoToSleepStates(DEVICE_STATE_FOLDED)
+ setFoldedStates(DEVICE_STATE_FOLDED.identifier)
+ setGoToSleepStates(DEVICE_STATE_FOLDED.identifier)
sut = FoldStateListener(mContext, listener)
}
@Test
fun onStateChanged_stateFolded_notifiesWithFoldedAndGoingToSleep() {
- sut.onStateChanged(DEVICE_STATE_FOLDED)
+ sut.onDeviceStateChanged(DEVICE_STATE_FOLDED)
verify(listener).onFoldStateChanged(FOLDED, WILL_GO_TO_SLEEP)
}
@Test
fun onStateChanged_stateHalfFolded_notifiesWithNotFoldedAndNotGoingToSleep() {
- sut.onStateChanged(DEVICE_STATE_HALF_FOLDED)
+ sut.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED)
verify(listener).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP)
}
@Test
fun onStateChanged_stateUnfolded_notifiesWithNotFoldedAndNotGoingToSleep() {
- sut.onStateChanged(DEVICE_STATE_UNFOLDED)
+ sut.onDeviceStateChanged(DEVICE_STATE_UNFOLDED)
verify(listener).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP)
}
@Test
fun onStateChanged_stateUnfoldedThenHalfFolded_notifiesOnce() {
- sut.onStateChanged(DEVICE_STATE_UNFOLDED)
- sut.onStateChanged(DEVICE_STATE_HALF_FOLDED)
+ sut.onDeviceStateChanged(DEVICE_STATE_UNFOLDED)
+ sut.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED)
verify(listener, times(1)).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP)
}
@Test
fun onStateChanged_stateHalfFoldedThenUnfolded_notifiesOnce() {
- sut.onStateChanged(DEVICE_STATE_HALF_FOLDED)
- sut.onStateChanged(DEVICE_STATE_UNFOLDED)
+ sut.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED)
+ sut.onDeviceStateChanged(DEVICE_STATE_UNFOLDED)
verify(listener, times(1)).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP)
}
@Test
fun onStateChanged_stateHalfFoldedThenFolded_notifiesTwice() {
- sut.onStateChanged(DEVICE_STATE_HALF_FOLDED)
- sut.onStateChanged(DEVICE_STATE_FOLDED)
+ sut.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED)
+ sut.onDeviceStateChanged(DEVICE_STATE_FOLDED)
val inOrder = Mockito.inOrder(listener)
inOrder.verify(listener).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP)
@@ -94,8 +95,8 @@
@Test
fun onStateChanged_stateFoldedThenHalfFolded_notifiesTwice() {
- sut.onStateChanged(DEVICE_STATE_FOLDED)
- sut.onStateChanged(DEVICE_STATE_HALF_FOLDED)
+ sut.onDeviceStateChanged(DEVICE_STATE_FOLDED)
+ sut.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED)
val inOrder = Mockito.inOrder(listener)
inOrder.verify(listener).onFoldStateChanged(FOLDED, WILL_GO_TO_SLEEP)
@@ -117,9 +118,18 @@
}
companion object {
- private const val DEVICE_STATE_FOLDED = 123
- private const val DEVICE_STATE_HALF_FOLDED = 456
- private const val DEVICE_STATE_UNFOLDED = 789
+ private val DEVICE_STATE_FOLDED = DeviceState(
+ DeviceState.Configuration.Builder(123 /* id */, "FOLDED" /* name */)
+ .build()
+ )
+ private val DEVICE_STATE_HALF_FOLDED = DeviceState(
+ DeviceState.Configuration.Builder(456 /* id */, "HALF_FOLDED" /* name */)
+ .build()
+ )
+ private val DEVICE_STATE_UNFOLDED = DeviceState(
+ DeviceState.Configuration.Builder(789 /* id */, "UNFOLDED" /* name */)
+ .build()
+ )
private const val FOLDED = true
private const val NOT_FOLDED = false
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 1748cff..d9e9c59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -177,7 +177,8 @@
new ConfigurationInteractor(new FakeConfigurationRepository()),
new FakeShadeRepository(),
keyguardTransitionInteractor,
- () -> mKosmos.getSceneInteractor());
+ () -> mKosmos.getSceneInteractor(),
+ () -> mKosmos.getFromGoneTransitionInteractor());
mViewModel =
new KeyguardStatusBarViewModel(
mTestScope.getBackgroundScope(),
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 443dd6a..1463680 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
@@ -38,6 +38,7 @@
import com.android.systemui.shade.ShadeControllerImpl
import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.window.StatusBarWindowStateController
@@ -67,6 +68,7 @@
class PhoneStatusBarViewControllerTest : SysuiTestCase() {
@Mock private lateinit var shadeViewController: ShadeViewController
+ @Mock private lateinit var panelExpansionInteractor: PanelExpansionInteractor
@Mock private lateinit var featureFlags: FeatureFlags
@Mock private lateinit var moveFromCenterAnimation: StatusBarMoveFromCenterAnimationController
@Mock private lateinit var sysuiUnfoldComponent: SysUIUnfoldComponent
@@ -195,7 +197,7 @@
@Test
fun handleTouchEventFromStatusBar_topEdgeTouch_viewNeverReceivesEvent() {
`when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
- `when`(shadeViewController.isFullyCollapsed).thenReturn(true)
+ `when`(panelExpansionInteractor.isFullyCollapsed).thenReturn(true)
val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
view.onTouchEvent(event)
@@ -291,21 +293,22 @@
private fun createAndInitController(view: PhoneStatusBarView): PhoneStatusBarViewController {
return PhoneStatusBarViewController.Factory(
- Optional.of(sysuiUnfoldComponent),
- Optional.of(progressProvider),
- featureFlags,
- FakeSceneContainerFlags(),
- userChipViewModel,
- centralSurfacesImpl,
- statusBarWindowStateController,
- shadeControllerImpl,
- shadeViewController,
- windowRootView,
- shadeLogger,
- viewUtil,
- configurationController,
- mStatusOverlayHoverListenerFactory
- )
+ Optional.of(sysuiUnfoldComponent),
+ Optional.of(progressProvider),
+ featureFlags,
+ FakeSceneContainerFlags(),
+ userChipViewModel,
+ centralSurfacesImpl,
+ statusBarWindowStateController,
+ shadeControllerImpl,
+ shadeViewController,
+ panelExpansionInteractor,
+ windowRootView,
+ shadeLogger,
+ viewUtil,
+ configurationController,
+ mStatusOverlayHoverListenerFactory
+ )
.create(view)
.also { it.init() }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 562aa6a..b0b9bec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -82,6 +82,9 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
+import com.android.systemui.keyguard.shared.model.TransitionState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.TaskbarDelegate;
import com.android.systemui.plugins.ActivityStarter;
@@ -103,6 +106,8 @@
import com.google.common.truth.Truth;
+import kotlin.Unit;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -1045,4 +1050,35 @@
verify(mCentralSurfaces, never()).hideKeyguard();
verify(mPrimaryBouncerInteractor, never()).show(true);
}
+
+ @Test
+ public void altBouncerNotVisible_keyguardAuthenticatedBiometricsHandled() {
+ clearInvocations(mAlternateBouncerInteractor);
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false);
+ mStatusBarKeyguardViewManager.consumeKeyguardAuthenticatedBiometricsHandled(Unit.INSTANCE);
+ verify(mAlternateBouncerInteractor, never()).hide();
+ }
+
+ @Test
+ public void altBouncerVisible_keyguardAuthenticatedBiometricsHandled() {
+ clearInvocations(mAlternateBouncerInteractor);
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+ mStatusBarKeyguardViewManager.consumeKeyguardAuthenticatedBiometricsHandled(Unit.INSTANCE);
+ verify(mAlternateBouncerInteractor).hide();
+ }
+
+ @Test
+ public void fromAlternateBouncerTransitionStep() {
+ clearInvocations(mAlternateBouncerInteractor);
+ mStatusBarKeyguardViewManager.consumeFromAlternateBouncerTransitionSteps(
+ new TransitionStep(
+ /* from */ KeyguardState.ALTERNATE_BOUNCER,
+ /* to */ KeyguardState.GONE,
+ /* value */ 1f,
+ TransitionState.FINISHED,
+ "StatusBarKeyguardViewManagerTest"
+ )
+ );
+ verify(mAlternateBouncerInteractor).hide();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 938b2f8..127a3d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -75,9 +75,9 @@
import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeControllerImpl;
-import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.data.repository.ShadeAnimationRepository;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationClickNotifier;
@@ -257,7 +257,7 @@
new StatusBarNotificationActivityStarterLogger(logcatLogBuffer()),
mOnUserInteractionCallback,
mock(NotificationPresenter.class),
- mock(ShadeViewController.class),
+ mock(PanelExpansionInteractor.class),
mock(NotificationShadeWindowController.class),
mActivityTransitionAnimator,
new ShadeAnimationInteractorLegacyImpl(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index b0404a0..a8c5fc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -50,6 +50,7 @@
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -296,6 +297,7 @@
mStatusBarNotificationPresenter = new StatusBarNotificationPresenter(
mContext,
shadeViewController,
+ mock(PanelExpansionInteractor.class),
mock(QuickSettingsController.class),
mock(HeadsUpManager.class),
notificationShadeWindowView,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index 3bf54a3..de18913 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.StatusBarStateControllerImpl
@@ -67,6 +68,8 @@
@Mock
private lateinit var shadeViewController: ShadeViewController
@Mock
+ private lateinit var panelExpansionInteractor: PanelExpansionInteractor
+ @Mock
private lateinit var notifShadeWindowController: NotificationShadeWindowController
@Mock
private lateinit var lightRevealScrim: LightRevealScrim
@@ -87,23 +90,20 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
controller = UnlockedScreenOffAnimationController(
- context,
- wakefulnessLifecycle,
- statusBarStateController,
- { keyguardViewMediator },
- keyguardStateController,
- { dozeParameters },
- globalSettings,
- { notifShadeWindowController },
- interactionJankMonitor,
- powerManager,
- handler = handler
+ context,
+ wakefulnessLifecycle,
+ statusBarStateController,
+ { keyguardViewMediator },
+ keyguardStateController,
+ { dozeParameters },
+ globalSettings,
+ { notifShadeWindowController },
+ interactionJankMonitor,
+ powerManager,
+ { panelExpansionInteractor },
+ handler,
)
controller.initialize(centralSurfaces, shadeViewController, lightRevealScrim)
-
- // Screen off does not run if the panel is expanded, so we should say it's collapsed to test
- // screen off.
- `when`(shadeViewController.isFullyCollapsed).thenReturn(true)
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index 1fb6e2c..c13e830 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -31,6 +31,7 @@
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_EMERGENCY
@@ -679,6 +680,9 @@
telephonyManager: TelephonyManager,
): MobileConnectionRepositoryImpl {
whenever(telephonyManager.subscriptionId).thenReturn(SUB_ID)
+ val systemUiCarrierConfigMock: SystemUiCarrierConfig = mock()
+ whenever(systemUiCarrierConfigMock.satelliteConnectionHysteresisSeconds)
+ .thenReturn(MutableStateFlow(0))
val realRepo =
MobileConnectionRepositoryImpl(
@@ -689,7 +693,7 @@
SEP,
connectivityManager,
telephonyManager,
- systemUiCarrierConfig = mock(),
+ systemUiCarrierConfig = systemUiCarrierConfigMock,
fakeBroadcastDispatcher,
mobileMappingsProxy = mock(),
testDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index 49953a1..c49fcf8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -43,12 +43,15 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.seconds
+import kotlin.time.DurationUnit
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -675,6 +678,32 @@
assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
}
+ @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ @Test
+ fun satBasedIcon_hasHysteresisWhenDisabled() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.signalLevelIcon)
+
+ val hysteresisDuration = 5.seconds
+ connectionRepository.satelliteConnectionHysteresisSeconds.value =
+ hysteresisDuration.toInt(DurationUnit.SECONDS)
+
+ connectionRepository.isNonTerrestrial.value = true
+
+ assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
+
+ // Disable satellite
+ connectionRepository.isNonTerrestrial.value = false
+
+ // Satellite icon should still be visible
+ assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
+
+ // Wait for the icon to change
+ advanceTimeBy(hysteresisDuration)
+
+ assertThat(latest).isInstanceOf(SignalIconModel.Cellular::class.java)
+ }
+
private fun createInteractor(
overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt
index ce47170..c606511 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy
+import android.hardware.devicestate.DeviceState
import android.hardware.devicestate.DeviceStateManager
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -30,6 +31,7 @@
import com.android.systemui.statusbar.policy.DevicePostureController.SUPPORTED_POSTURES_SIZE
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -72,6 +74,18 @@
com.android.internal.R.array.config_device_state_postures,
deviceStateToPostureMapping
)
+ whenever(deviceStateManager.supportedDeviceStates)
+ .thenReturn(
+ listOf(
+ DEVICE_STATE_CLOSED,
+ DEVICE_STATE_HALF_FOLDED,
+ DEVICE_STATE_OPENED,
+ DEVICE_STATE_FLIPPED,
+ DEVICE_STATE_UNKNOWN,
+ DEVICE_STATE_USE_BASE_STATE
+ )
+ )
+
underTest =
DevicePostureControllerImpl(
context,
@@ -86,20 +100,20 @@
var posture = -1
underTest.addCallback { posture = it }
- deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_UNKNOWN)
- assertThat(posture).isEqualTo(DEVICE_POSTURE_UNKNOWN)
-
- deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_CLOSED)
+ deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_CLOSED)
assertThat(posture).isEqualTo(DEVICE_POSTURE_CLOSED)
- deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_HALF_OPENED)
+ deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED)
assertThat(posture).isEqualTo(DEVICE_POSTURE_HALF_OPENED)
- deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_OPENED)
+ deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_OPENED)
assertThat(posture).isEqualTo(DEVICE_POSTURE_OPENED)
- deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_FLIPPED)
+ deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_FLIPPED)
assertThat(posture).isEqualTo(DEVICE_POSTURE_FLIPPED)
+
+ deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_UNKNOWN)
+ assertThat(posture).isEqualTo(DEVICE_POSTURE_UNKNOWN)
}
@Test
@@ -107,15 +121,26 @@
var posture = -1
underTest.addCallback { posture = it }
- deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_HALF_OPENED)
+ deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED)
assertThat(posture).isEqualTo(DEVICE_POSTURE_HALF_OPENED)
- // base state change doesn't change the posture
- deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_CLOSED)
+ val physicalProperties =
+ setOf(DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED)
+ val updatedState =
+ DeviceState(
+ DeviceState.Configuration.Builder(
+ DEVICE_STATE_HALF_FOLDED.identifier,
+ DEVICE_STATE_HALF_FOLDED.name
+ )
+ .setPhysicalProperties(physicalProperties)
+ .build()
+ )
+ // state change with updated physical properties shouldn't cause a posture change
+ deviceStateCallback.value.onDeviceStateChanged(updatedState)
assertThat(posture).isEqualTo(DEVICE_POSTURE_HALF_OPENED)
- // WHEN the display state maps to using the base state, then posture updates
- deviceStateCallback.value.onStateChanged(useBaseStateDeviceState)
+ // WHEN the display state maps to the physical state, then posture updates
+ deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_USE_BASE_STATE)
assertThat(posture).isEqualTo(DEVICE_POSTURE_CLOSED)
}
@@ -124,20 +149,97 @@
var numPostureChanges = 0
underTest.addCallback { numPostureChanges++ }
- deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_HALF_OPENED)
+ deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED)
assertThat(numPostureChanges).isEqualTo(1)
- // base state changes doesn't send another posture update since the device state isn't
- // useBaseStateDeviceState
- deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_CLOSED)
- deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_HALF_OPENED)
- deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_FLIPPED)
- deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_OPENED)
- deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_UNKNOWN)
+ // update to physical properties doesn't send another posture update since the device state
+ // isn't useBaseStateDeviceState
+ deviceStateCallback.value.onDeviceStateChanged(
+ getStateUpdatedPhysicalProperties(DEVICE_STATE_HALF_FOLDED, DEVICE_STATE_CLOSED)
+ )
+ deviceStateCallback.value.onDeviceStateChanged(
+ getStateUpdatedPhysicalProperties(DEVICE_STATE_HALF_FOLDED, DEVICE_STATE_HALF_FOLDED)
+ )
+ deviceStateCallback.value.onDeviceStateChanged(
+ getStateUpdatedPhysicalProperties(DEVICE_STATE_HALF_FOLDED, DEVICE_STATE_OPENED)
+ )
+ deviceStateCallback.value.onDeviceStateChanged(
+ getStateUpdatedPhysicalProperties(DEVICE_STATE_HALF_FOLDED, DEVICE_STATE_UNKNOWN)
+ )
assertThat(numPostureChanges).isEqualTo(1)
}
private fun verifyRegistersForDeviceStateCallback() {
verify(deviceStateManager).registerCallback(eq(mainExecutor), deviceStateCallback.capture())
}
+
+ private fun getStateUpdatedPhysicalProperties(
+ currentState: DeviceState,
+ physicalState: DeviceState
+ ): DeviceState {
+ return DeviceState(
+ DeviceState.Configuration.Builder(currentState.identifier, currentState.name)
+ .setSystemProperties(currentState.configuration.systemProperties)
+ .setPhysicalProperties(physicalState.configuration.physicalProperties)
+ .build()
+ )
+ }
+
+ companion object {
+ val DEVICE_STATE_CLOSED =
+ DeviceState(
+ DeviceState.Configuration.Builder(
+ DEVICE_POSTURE_CLOSED /* id */,
+ "CLOSED" /* name */
+ )
+ .setPhysicalProperties(
+ setOf(DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED)
+ )
+ .build()
+ )
+ val DEVICE_STATE_HALF_FOLDED =
+ DeviceState(
+ DeviceState.Configuration.Builder(
+ DEVICE_POSTURE_HALF_OPENED /* id */,
+ "HALF_FOLDED" /* name */
+ )
+ .setPhysicalProperties(
+ setOf(
+ DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN
+ )
+ )
+ .build()
+ )
+ val DEVICE_STATE_OPENED =
+ DeviceState(
+ DeviceState.Configuration.Builder(
+ DEVICE_POSTURE_OPENED /* id */,
+ "OPENED" /* name */
+ )
+ .setPhysicalProperties(
+ setOf(DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN)
+ )
+ .build()
+ )
+ val DEVICE_STATE_FLIPPED =
+ DeviceState(
+ DeviceState.Configuration.Builder(
+ DEVICE_POSTURE_FLIPPED /* id */,
+ "FLIPPED" /* name */
+ )
+ .build()
+ )
+ val DEVICE_STATE_UNKNOWN =
+ DeviceState(
+ DeviceState.Configuration.Builder(
+ DEVICE_POSTURE_UNKNOWN /* id */,
+ "UNKNOWN" /* name */
+ )
+ .build()
+ )
+ val DEVICE_STATE_USE_BASE_STATE =
+ DeviceState(
+ DeviceState.Configuration.Builder(SUPPORTED_POSTURES_SIZE, "USE_BASE_STATE").build()
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
index 4ccbd1b..2955162 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
@@ -25,6 +25,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
+import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateManager;
import android.os.UserHandle;
import android.provider.Settings;
@@ -119,11 +120,11 @@
0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
mFakeRotationPolicy.setRotationLock(true);
- mDeviceStateCallback.onStateChanged(1);
+ mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(1));
assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
// Settings only exist for state 0 and 1
- mDeviceStateCallback.onStateChanged(2);
+ mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2));
assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
}
@@ -134,10 +135,10 @@
0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 1, DEVICE_STATE_ROTATION_LOCK_LOCKED);
mFakeRotationPolicy.setRotationLock(true);
- mDeviceStateCallback.onStateChanged(0);
+ mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0));
assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
- mDeviceStateCallback.onStateChanged(1);
+ mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(1));
assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue();
}
@@ -147,7 +148,7 @@
mFakeRotationPolicy.setRotationLock(true);
// State 2 -> Ignored -> Fall back to state 1 which is unlocked
- mDeviceStateCallback.onStateChanged(2);
+ mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2));
assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
}
@@ -161,7 +162,7 @@
mFakeRotationPolicy.setRotationLock(false);
// State 2 -> Ignored -> Fall back to state 1 which is locked
- mDeviceStateCallback.onStateChanged(2);
+ mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2));
assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue();
}
@@ -173,7 +174,7 @@
mSettingsManager.onPersistedSettingsChanged();
mFakeRotationPolicy.setRotationLock(true);
- mDeviceStateCallback.onStateChanged(0);
+ mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0));
assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue();
mDeviceStateRotationLockSettingController.onRotationLockStateChanged(
@@ -189,10 +190,10 @@
@Test
public void whenDeviceStateSwitchedToIgnoredState_useFallbackSetting() {
- mDeviceStateCallback.onStateChanged(0);
+ mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0));
assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue();
- mDeviceStateCallback.onStateChanged(2);
+ mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2));
assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
}
@@ -202,10 +203,10 @@
8, DEVICE_STATE_ROTATION_LOCK_IGNORED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
mFakeRotationPolicy.setRotationLock(true);
- mDeviceStateCallback.onStateChanged(1);
+ mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(1));
assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
- mDeviceStateCallback.onStateChanged(8);
+ mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(8));
assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
mDeviceStateRotationLockSettingController.onRotationLockStateChanged(
@@ -225,7 +226,7 @@
0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED,
1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
mFakeRotationPolicy.setRotationLock(false);
- mDeviceStateCallback.onStateChanged(0);
+ mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0));
assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
@@ -241,7 +242,7 @@
initializeSettingsWith(
0, DEVICE_STATE_ROTATION_LOCK_LOCKED,
1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
- mDeviceStateCallback.onStateChanged(0);
+ mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0));
mDeviceStateRotationLockSettingController.onRotationLockStateChanged(
/* rotationLocked= */ false,
@@ -262,7 +263,7 @@
0, DEVICE_STATE_ROTATION_LOCK_LOCKED,
1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED,
2, DEVICE_STATE_ROTATION_LOCK_IGNORED);
- mDeviceStateCallback.onStateChanged(2);
+ mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2));
mDeviceStateRotationLockSettingController.onRotationLockStateChanged(
/* rotationLocked= */ true,
@@ -283,8 +284,8 @@
0, DEVICE_STATE_ROTATION_LOCK_LOCKED,
1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED,
8, DEVICE_STATE_ROTATION_LOCK_IGNORED);
- mDeviceStateCallback.onStateChanged(1);
- mDeviceStateCallback.onStateChanged(8);
+ mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(1));
+ mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(8));
mDeviceStateRotationLockSettingController.onRotationLockStateChanged(
/* rotationLocked= */ true,
@@ -320,6 +321,10 @@
mSettingsManager.onPersistedSettingsChanged();
}
+ private DeviceState createDeviceStateForIdentifier(int id) {
+ return new DeviceState(new DeviceState.Configuration.Builder(id, "" /* name */).build());
+ }
+
private static class FakeRotationPolicy implements RotationPolicyWrapper {
private boolean mRotationLock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index 76913e8..e4b9f10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -24,6 +24,7 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.fromGoneTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.kosmos.testScope
@@ -63,9 +64,9 @@
ConfigurationInteractor(FakeConfigurationRepository()),
FakeShadeRepository(),
kosmos.keyguardTransitionInteractor,
- ) {
- kosmos.sceneInteractor
- }
+ { kosmos.sceneInteractor },
+ { kosmos.fromGoneTransitionInteractor },
+ )
private val keyguardStatusBarInteractor =
KeyguardStatusBarInteractor(
FakeKeyguardStatusBarRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 19f31d5..ec27f48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -441,7 +441,8 @@
new ConfigurationInteractor(configurationRepository),
shadeRepository,
keyguardTransitionInteractor,
- () -> sceneInteractor);
+ () -> sceneInteractor,
+ () -> mKosmos.getFromGoneTransitionInteractor());
mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
mFromPrimaryBouncerTransitionInteractor =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
index 3428553..8001604 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
@@ -30,6 +30,7 @@
*
* To use:
* - locally edit your test class to inherit from MemoryTrackingTestCase instead of SysuiTestCase
+ * - Use `atest -d` to prevent files being cleaned up
* - Watch the logcat with tag MEMORY to see the path to the .ahprof file
* - adb pull /path/to/something.ahprof
* - Download ahat from https://sites.google.com/corp/google.com/ahat/home
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
index cceb3ff..f65c74f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
@@ -19,6 +19,7 @@
import android.platform.test.annotations.EnableFlags
import com.android.systemui.Flags.FLAG_COMPOSE_LOCKSCREEN
import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
+import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.Flags.FLAG_MEDIA_IN_SCENE_CONTAINER
import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
@@ -33,6 +34,7 @@
FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
FLAG_COMPOSE_LOCKSCREEN,
FLAG_MEDIA_IN_SCENE_CONTAINER,
+ FLAG_KEYGUARD_WM_STATE_REFACTOR,
)
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/jank/InteractionJankMonitorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/jank/InteractionJankMonitorKosmos.kt
index 5c5016d..e2b5869 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/jank/InteractionJankMonitorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/jank/InteractionJankMonitorKosmos.kt
@@ -16,9 +16,24 @@
package com.android.systemui.jank
+import android.os.HandlerThread
import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.jank.InteractionJankMonitor.Configuration.Builder
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.spy
-val Kosmos.interactionJankMonitor by Fixture<InteractionJankMonitor> { mock() }
+val Kosmos.interactionJankMonitor by
+ Fixture<InteractionJankMonitor> {
+ spy(InteractionJankMonitor(HandlerThread("InteractionJankMonitor-Kosmos"))).apply {
+ doReturn(true).`when`(this).shouldMonitor()
+ doReturn(true).`when`(this).begin(any(), anyInt())
+ doReturn(true).`when`(this).begin(any<Builder>())
+ doReturn(true).`when`(this).end(anyInt())
+ doReturn(true).`when`(this).cancel(anyInt())
+ doReturn(true).`when`(this).cancel(anyInt(), anyInt())
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
index 3893a9b7..00cdc33 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -51,6 +51,7 @@
configurationRepository: FakeConfigurationRepository = FakeConfigurationRepository(),
shadeRepository: FakeShadeRepository = FakeShadeRepository(),
sceneInteractor: SceneInteractor = mock(),
+ fromGoneTransitionInteractor: FromGoneTransitionInteractor = mock(),
powerInteractor: PowerInteractor = PowerInteractorFactory.create().powerInteractor,
): WithDependencies {
// Mock this until the class is replaced by kosmos
@@ -77,6 +78,7 @@
sceneInteractorProvider = { sceneInteractor },
keyguardTransitionInteractor = keyguardTransitionInteractor,
powerInteractor = powerInteractor,
+ fromGoneTransitionInteractor = { fromGoneTransitionInteractor },
),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
index 5140a9f..d61bc9f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
@@ -26,7 +26,7 @@
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.statusbar.commandQueue
-val Kosmos.keyguardInteractor by
+val Kosmos.keyguardInteractor: KeyguardInteractor by
Kosmos.Fixture {
KeyguardInteractor(
repository = keyguardRepository,
@@ -38,5 +38,6 @@
shadeRepository = shadeRepository,
keyguardTransitionInteractor = keyguardTransitionInteractor,
sceneInteractorProvider = { sceneInteractor },
+ fromGoneTransitionInteractor = { fromGoneTransitionInteractor },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
index 6cc1e8e..185deda 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
@@ -20,11 +20,13 @@
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by
Kosmos.Fixture {
KeyguardTransitionInteractor(
scope = applicationCoroutineScope,
+ mainDispatcher = testDispatcher,
repository = keyguardTransitionRepository,
keyguardRepository = keyguardRepository,
fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
index d84988d..29167d6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
@@ -27,6 +27,7 @@
surfaceBehindInteractor = keyguardSurfaceBehindInteractor,
fromLockscreenInteractor = fromLockscreenTransitionInteractor,
fromBouncerInteractor = fromPrimaryBouncerTransitionInteractor,
+ fromAlternateBouncerInteractor = fromAlternateBouncerTransitionInteractor,
notificationLaunchAnimationInteractor = notificationLaunchAnimationInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 3fc5af1..e861892 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -34,6 +34,7 @@
import com.android.systemui.jank.interactionJankMonitor
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.fromGoneTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
@@ -92,6 +93,7 @@
val fromPrimaryBouncerTransitionInteractor by lazy {
kosmos.fromPrimaryBouncerTransitionInteractor
}
+ val fromGoneTransitionInteractor by lazy { kosmos.fromGoneTransitionInteractor }
val globalActionsInteractor by lazy { kosmos.globalActionsInteractor }
val sceneDataSource by lazy { kosmos.sceneDataSource }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/scroll/FakeScrollCaptureConnection.java
similarity index 97%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/scroll/FakeScrollCaptureConnection.java
index 63f7c97..ea59c0a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/scroll/FakeScrollCaptureConnection.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
import android.content.pm.ActivityInfo;
import android.graphics.Canvas;
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeSession.java b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/scroll/FakeSession.java
similarity index 97%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeSession.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/scroll/FakeSession.java
index 478658e..3b7b158 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeSession.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/scroll/FakeSession.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.scroll;
import static android.util.MathUtils.constrain;
@@ -32,6 +32,8 @@
import android.media.Image;
import android.util.Log;
+import com.android.systemui.screenshot.scroll.ScrollCaptureClient;
+
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt
index 2a4dd3a..09c8f87 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt
@@ -23,6 +23,8 @@
val Kosmos.panelExpansionInteractor by Fixture { panelExpansionInteractorImpl }
val Kosmos.panelExpansionInteractorImpl by Fixture {
PanelExpansionInteractorImpl(
- sceneInteractor = sceneInteractor,
+ sceneInteractor,
+ shadeInteractor,
+ shadeAnimationInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt
index d2dd200..6d24e2a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade.domain.interactor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.data.repository.shadeAnimationRepository
@@ -24,5 +25,9 @@
Kosmos.Fixture { ShadeAnimationInteractorEmptyImpl(shadeAnimationRepository) }
var Kosmos.shadeAnimationInteractorSceneContainerImpl: ShadeAnimationInteractorSceneContainerImpl by
Kosmos.Fixture {
- ShadeAnimationInteractorSceneContainerImpl(shadeAnimationRepository, sceneInteractor)
+ ShadeAnimationInteractorSceneContainerImpl(
+ testScope.backgroundScope,
+ shadeAnimationRepository,
+ sceneInteractor
+ )
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardViewOcclusionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractorKosmos.kt
similarity index 77%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardViewOcclusionInteractorKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractorKosmos.kt
index 9e34fe8..2ed56ce 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardViewOcclusionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractorKosmos.kt
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar.domain.interactor
+import com.android.systemui.keyguard.domain.interactor.keyguardSurfaceBehindInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.windowManagerLockscreenVisibilityInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.power.domain.interactor.powerInteractor
@@ -26,5 +28,7 @@
keyguardTransitionInteractor = this.keyguardTransitionInteractor,
keyguardOcclusionInteractor = this.keyguardOcclusionInteractor,
powerInteractor = this.powerInteractor,
+ wmLockscreenVisibilityInteractor = windowManagerLockscreenVisibilityInteractor,
+ surfaceBehindInteractor = keyguardSurfaceBehindInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
index 7a86c4f..c6684af 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
@@ -30,9 +30,9 @@
import com.android.systemui.plugins.activityStarter
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.settings.userTracker
+import com.android.systemui.shade.domain.interactor.panelExpansionInteractor
import com.android.systemui.shade.domain.interactor.shadeAnimationInteractor
import com.android.systemui.shade.shadeController
-import com.android.systemui.shade.shadeViewController
import com.android.systemui.statusbar.commandQueue
import com.android.systemui.statusbar.notification.collection.provider.launchFullScreenIntentProvider
import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider
@@ -78,7 +78,7 @@
statusBarNotificationActivityStarterLogger,
onUserInteractionCallback,
notificationPresenter,
- shadeViewController,
+ panelExpansionInteractor,
notificationShadeWindowController,
activityTransitionAnimator,
shadeAnimationInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index 32d572e..2d5a361 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -63,6 +63,8 @@
override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false)
+ override val satelliteConnectionHysteresisSeconds = MutableStateFlow(0)
+
private var isInEcmMode: Boolean = false
override suspend fun isInEcmMode(): Boolean = isInEcmMode
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt
index aa2c2a2..ebcabf8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt
@@ -39,8 +39,16 @@
callbacks.toSet().forEach { it.onUserSwitched() }
}
- fun setUserSetup(userId: Int) {
- usersSetup.add(userId)
+ fun setUserSetup(userId: Int, isSetup: Boolean = true) {
+ if (isSetup) {
+ usersSetup.add(userId)
+ } else {
+ usersSetup.remove(userId)
+ }
callbacks.toSet().forEach { it.onUserSetupChanged() }
}
+
+ fun setCurrentUserSetup(isSetup: Boolean) {
+ setUserSetup(currentUser, isSetup)
+ }
}
diff --git a/ravenwood/README.md b/ravenwood/README.md
index 9c4fda7..8cafb43 100644
--- a/ravenwood/README.md
+++ b/ravenwood/README.md
@@ -1,9 +1,11 @@
# Ravenwood
-Ravenwood is an officially-supported lightweight unit testing environment for Android platform code that runs on the host.
+Ravenwood is a lightweight unit testing environment for Android platform code that runs on the host.
Ravenwood’s focus on Android platform use-cases, improved maintainability, and device consistency distinguishes it from Robolectric, which remains a popular choice for app testing.
+> **Note:** Active development of Ravenwood has been paused as of March 2024. Existing Ravenwood tests will continue running, but support has moved to a self-service model.
+
## Background
Executing tests on a typical Android device has substantial overhead, such as flashing the build, waiting for the boot to complete, and retrying tests that fail due to general flakiness.
diff --git a/ravenwood/api-maintainers.md b/ravenwood/api-maintainers.md
index 4b2f968..c059cab 100644
--- a/ravenwood/api-maintainers.md
+++ b/ravenwood/api-maintainers.md
@@ -4,7 +4,7 @@
To opt-in to supporting an API under Ravenwood, you can use the inline annotations documented below to customize your API behavior when running under Ravenwood. Because these annotations are inline in the relevant platform source code, they serve as valuable reminders to future API maintainers of Ravenwood support expectations.
-> **Note:** to ensure that API teams are well-supported during early Ravenwood onboarding, the Ravenwood team is manually maintaining an allow-list of classes that are able to use Ravenwood annotations. Please reach out to ravenwood@ so we can offer design advice and allow-list your APIs.
+> **Note:** Active development of Ravenwood has been paused as of March 2024. Currently supported APIs will continue working, but the addition of new APIs is not currently being supported. There is an allowlist that restricts where Ravenwood-specific annotations can be used, and that allowlist is not being expanded while development is paused.
These Ravenwood-specific annotations have no bearing on the status of an API being public, `@SystemApi`, `@TestApi`, `@hide`, etc. Ravenwood annotations are an orthogonal concept that are only consumed by the internal `hoststubgen` tool during a post-processing step that generates the Ravenwood runtime environment. Teams that own APIs can continue to refactor opted-in `@hide` implementation details, as long as the test-visible behavior continues passing.
diff --git a/services/Android.bp b/services/Android.bp
index 8709692..98a7979 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -119,6 +119,7 @@
":services.companion-sources",
":services.contentcapture-sources",
":services.contentsuggestions-sources",
+ ":services.contextualsearch-sources",
":services.coverage-sources",
":services.credentials-sources",
":services.devicepolicy-sources",
@@ -208,6 +209,7 @@
"services.companion",
"services.contentcapture",
"services.contentsuggestions",
+ "services.contextualsearch",
"services.coverage",
"services.credentials",
"services.devicepolicy",
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 4be303a..4e14dee 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -935,34 +935,11 @@
}
final AccessibilityUserState userState = getUserStateLocked(userId);
- if (Flags.disableContinuousShortcutOnForceStop()) {
- if (doit && onPackagesForceStoppedLocked(packages, userState)) {
- onUserStateChangedLocked(userState);
- return false;
- } else {
- return true;
- }
- } else {
- final Iterator<ComponentName> it = userState.mEnabledServices.iterator();
- while (it.hasNext()) {
- final ComponentName comp = it.next();
- final String compPkg = comp.getPackageName();
- for (String pkg : packages) {
- if (compPkg.equals(pkg)) {
- if (!doit) {
- return true;
- }
- it.remove();
- userState.getBindingServicesLocked().remove(comp);
- userState.getCrashedServicesLocked().remove(comp);
- persistComponentNamesToSettingLocked(
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
- userState.mEnabledServices, userId);
- onUserStateChangedLocked(userState);
- }
- }
- }
+ if (doit && onPackagesForceStoppedLocked(packages, userState)) {
+ onUserStateChangedLocked(userState);
return false;
+ } else {
+ return true;
}
}
}
@@ -1706,20 +1683,101 @@
}
@Override
- @RequiresPermission(Manifest.permission.STATUS_BAR_SERVICE)
+ @RequiresPermission(allOf = {
+ Manifest.permission.STATUS_BAR_SERVICE,
+ Manifest.permission.MANAGE_ACCESSIBILITY
+ })
public void notifyQuickSettingsTilesChanged(
- @UserIdInt int userId, List<ComponentName> tileComponentNames) {
- mSecurityPolicy.enforceCallingPermission(
+ @UserIdInt int userId, @NonNull List<ComponentName> tileComponentNames) {
+ if (!android.view.accessibility.Flags.a11yQsShortcut()) {
+ return;
+ }
+
+ mContext.enforceCallingPermission(
Manifest.permission.STATUS_BAR_SERVICE,
/* function= */ "notifyQuickSettingsTilesChanged");
+ mContext.enforceCallingPermission(
+ Manifest.permission.MANAGE_ACCESSIBILITY,
+ /* function= */ "notifyQuickSettingsTilesChanged");
- Slog.d(LOG_TAG, TextUtils.formatSimple(
- "notifyQuickSettingsTilesChanged userId: %d, tileComponentNames: %s",
- userId, tileComponentNames));
- // TODO (b/314843909): in the follow up cl
+ if (DEBUG) {
+ Slog.d(LOG_TAG, TextUtils.formatSimple(
+ "notifyQuickSettingsTilesChanged userId: %d, tileComponentNames: %s",
+ userId, tileComponentNames));
+ }
+ final Set<ComponentName> newTileComponentNames = new ArraySet<>(tileComponentNames);
+ final Set<ComponentName> addedTiles;
+ final Set<ComponentName> removedTiles;
+ final Map<ComponentName, AccessibilityServiceInfo> tileServiceToA11yServiceInfo;
+ final Map<ComponentName, ComponentName> a11yFeatureToTileService;
+
// update in-memory copy of QS_TILES in AccessibilityManager
- // update Settings.Secure.ACCESSIBILITY_QS_TARGETS and its in-memory copy
- // show full device control warning if needed (b/314850435)
+ synchronized (mLock) {
+ AccessibilityUserState userState = getUserStateLocked(userId);
+
+ tileServiceToA11yServiceInfo = userState.getTileServiceToA11yServiceInfoMapLocked();
+ a11yFeatureToTileService = userState.getA11yFeatureToTileService();
+
+ ArraySet<ComponentName> currentTiles = userState.getA11yQsTilesInQsPanel();
+ // Find newly added tiles
+ addedTiles = newTileComponentNames
+ .stream()
+ .filter(tileComponentName -> !currentTiles.contains(tileComponentName))
+ .collect(Collectors.toSet());
+ // Find newly removed tiles
+ removedTiles = currentTiles
+ .stream()
+ .filter(tileComponentName -> !newTileComponentNames.contains(tileComponentName))
+ .collect(Collectors.toSet());
+
+ if (addedTiles.isEmpty() && removedTiles.isEmpty()) {
+ return;
+ }
+
+ userState.updateA11yTilesInQsPanelLocked(newTileComponentNames);
+ }
+
+ List<String> a11yFeaturesToEnable = new ArrayList<>();
+ List<String> a11yFeaturesToRemove = new ArrayList<>();
+ // Find the framework features to configure the qs shortcut on/off
+ for (Map.Entry<ComponentName, ComponentName> frameworkFeatureWithTile :
+ ShortcutConstants.A11Y_FEATURE_TO_FRAMEWORK_TILE.entrySet()) {
+ String a11yFeature = frameworkFeatureWithTile.getKey().flattenToString();
+ ComponentName tile = frameworkFeatureWithTile.getValue();
+ if (addedTiles.contains(tile)) {
+ a11yFeaturesToEnable.add(a11yFeature);
+ } else if (removedTiles.contains(tile)) {
+ a11yFeaturesToRemove.add(a11yFeature);
+ }
+ }
+ // Find the accessibility service/activity to configure the qs shortcut on/off
+ for (Map.Entry<ComponentName, ComponentName> a11yFeatureWithTileService :
+ a11yFeatureToTileService.entrySet()) {
+ String a11yFeature = a11yFeatureWithTileService.getKey().flattenToString();
+ ComponentName tileService = a11yFeatureWithTileService.getValue();
+ if (addedTiles.contains(tileService)) {
+ AccessibilityServiceInfo serviceInfo = tileServiceToA11yServiceInfo.getOrDefault(
+ tileService, null);
+ if (serviceInfo != null && isAccessibilityServiceWarningRequired(serviceInfo)) {
+ // TODO(b/314850435): show full device control warning if needed after
+ // SysUI QS Panel can update live
+ continue;
+ }
+ a11yFeaturesToEnable.add(a11yFeature);
+ } else if (removedTiles.contains(tileService)) {
+ a11yFeaturesToRemove.add(a11yFeature);
+ }
+ }
+ // Turn on/off a11y qs shortcut for the a11y features based on the change in QS Panel
+ if (!a11yFeaturesToEnable.isEmpty()) {
+ enableShortcutForTargets(/* enable= */ true, UserShortcutType.QUICK_SETTINGS,
+ a11yFeaturesToEnable, userId);
+ }
+
+ if (!a11yFeaturesToRemove.isEmpty()) {
+ enableShortcutForTargets(/* enable= */ false, UserShortcutType.QUICK_SETTINGS,
+ a11yFeaturesToRemove, userId);
+ }
}
/**
@@ -3661,18 +3719,35 @@
/**
* Update the Settings.Secure.ACCESSIBILITY_QS_TARGETS so that it only contains valid content,
* and a side loaded service can't spoof the package name of the default service.
+ * <p>
+ * 1. Remove the target if the target is no longer installed on the device <br/>
+ * 2. Add the target if the target is enabled and the target's tile is in the QS Panel <br/>
+ * </p>
*/
private void updateAccessibilityQsTargetsLocked(AccessibilityUserState userState) {
- final Set<String> targets =
- userState.getShortcutTargetsLocked(UserShortcutType.QUICK_SETTINGS);
- final int lastSize = targets.size();
- if (lastSize == 0) {
+ if (!android.view.accessibility.Flags.a11yQsShortcut()) {
return;
}
+ final Set<String> targets =
+ userState.getShortcutTargetsLocked(UserShortcutType.QUICK_SETTINGS);
+
// Removes the targets that are no longer installed on the device.
boolean somethingChanged = targets.removeIf(
name -> !userState.isShortcutTargetInstalledLocked(name));
+ // Add the target if the a11y service is enabled and the tile exist in QS panel
+ Set<ComponentName> enabledServices = userState.getEnabledServicesLocked();
+ Map<ComponentName, ComponentName> a11yFeatureToTileService =
+ userState.getA11yFeatureToTileService();
+ Set<ComponentName> currentA11yTilesInQsPanel = userState.getA11yQsTilesInQsPanel();
+ for (ComponentName enabledService : enabledServices) {
+ ComponentName tileService =
+ a11yFeatureToTileService.getOrDefault(enabledService, null);
+ if (tileService != null && currentA11yTilesInQsPanel.contains(tileService)) {
+ somethingChanged |= targets.add(enabledService.flattenToString());
+ }
+ }
+
if (!somethingChanged) {
return;
}
@@ -3700,14 +3775,18 @@
return;
}
- final List<Pair<Integer, String>> shortcutTypeAndShortcutSetting = List.of(
+ final List<Pair<Integer, String>> shortcutTypeAndShortcutSetting = new ArrayList<>(3);
+ shortcutTypeAndShortcutSetting.add(
new Pair<>(ACCESSIBILITY_SHORTCUT_KEY,
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE));
+ shortcutTypeAndShortcutSetting.add(
new Pair<>(ACCESSIBILITY_BUTTON,
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
- new Pair<>(UserShortcutType.QUICK_SETTINGS,
- Settings.Secure.ACCESSIBILITY_QS_TARGETS)
- );
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS));
+ if (android.view.accessibility.Flags.a11yQsShortcut()) {
+ shortcutTypeAndShortcutSetting.add(
+ new Pair<>(UserShortcutType.QUICK_SETTINGS,
+ Settings.Secure.ACCESSIBILITY_QS_TARGETS));
+ }
final ComponentName serviceName = service.getComponentName();
for (Pair<Integer, String> shortcutTypePair : shortcutTypeAndShortcutSetting) {
@@ -5123,18 +5202,16 @@
private int mSystemUiUid = 0;
AccessibilityDisplayListener(Context context, Handler handler) {
- if (Flags.addWindowTokenWithoutLock()) {
- // Avoid concerns about one thread adding displays while another thread removes
- // them by ensuring the looper is the main looper and the DisplayListener
- // callbacks are always executed on the one main thread.
- final boolean isMainHandler = handler.getLooper() == Looper.getMainLooper();
- final String errorMessage =
- "AccessibilityDisplayListener must use the main handler";
- if (Build.IS_USERDEBUG || Build.IS_ENG) {
- Preconditions.checkArgument(isMainHandler, errorMessage);
- } else if (!isMainHandler) {
- Slog.e(LOG_TAG, errorMessage);
- }
+ // Avoid concerns about one thread adding displays while another thread removes
+ // them by ensuring the looper is the main looper and the DisplayListener
+ // callbacks are always executed on the one main thread.
+ final boolean isMainHandler = handler.getLooper() == Looper.getMainLooper();
+ final String errorMessage =
+ "AccessibilityDisplayListener must use the main handler";
+ if (Build.IS_USERDEBUG || Build.IS_ENG) {
+ Preconditions.checkArgument(isMainHandler, errorMessage);
+ } else if (!isMainHandler) {
+ Slog.e(LOG_TAG, errorMessage);
}
mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
@@ -5178,14 +5255,12 @@
@Override
public void onDisplayAdded(int displayId) {
- if (Flags.addWindowTokenWithoutLock()) {
- final boolean isMainThread = Looper.getMainLooper().isCurrentThread();
- final String errorMessage = "onDisplayAdded must be called from the main thread";
- if (Build.IS_USERDEBUG || Build.IS_ENG) {
- Preconditions.checkArgument(isMainThread, errorMessage);
- } else if (!isMainThread) {
- Slog.e(LOG_TAG, errorMessage);
- }
+ final boolean isMainThread = Looper.getMainLooper().isCurrentThread();
+ final String errorMessage = "onDisplayAdded must be called from the main thread";
+ if (Build.IS_USERDEBUG || Build.IS_ENG) {
+ Preconditions.checkArgument(isMainThread, errorMessage);
+ } else if (!isMainThread) {
+ Slog.e(LOG_TAG, errorMessage);
}
final Display display = mDisplayManager.getDisplay(displayId);
if (!isValidDisplay(display)) {
@@ -5201,41 +5276,27 @@
mInputFilter.onDisplayAdded(display);
}
AccessibilityUserState userState = getCurrentUserStateLocked();
- if (Flags.addWindowTokenWithoutLock()) {
- services = new ArrayList<>(userState.mBoundServices);
- } else {
- services = userState.mBoundServices;
- if (displayId != Display.DEFAULT_DISPLAY) {
- for (int i = 0; i < services.size(); i++) {
- AccessibilityServiceConnection boundClient = services.get(i);
- boundClient.addWindowTokenForDisplay(displayId);
- }
- }
- }
+ services = new ArrayList<>(userState.mBoundServices);
updateMagnificationLocked(userState);
updateWindowsForAccessibilityCallbackLocked(userState);
notifyClearAccessibilityCacheLocked();
}
- if (Flags.addWindowTokenWithoutLock()) {
- if (displayId != Display.DEFAULT_DISPLAY) {
- for (int i = 0; i < services.size(); i++) {
- AccessibilityServiceConnection boundClient = services.get(i);
- boundClient.addWindowTokenForDisplay(displayId);
- }
+ if (displayId != Display.DEFAULT_DISPLAY) {
+ for (int i = 0; i < services.size(); i++) {
+ AccessibilityServiceConnection boundClient = services.get(i);
+ boundClient.addWindowTokenForDisplay(displayId);
}
}
}
@Override
public void onDisplayRemoved(int displayId) {
- if (Flags.addWindowTokenWithoutLock()) {
- final boolean isMainThread = Looper.getMainLooper().isCurrentThread();
- final String errorMessage = "onDisplayRemoved must be called from the main thread";
- if (Build.IS_USERDEBUG || Build.IS_ENG) {
- Preconditions.checkArgument(isMainThread, errorMessage);
- } else if (!isMainThread) {
- Slog.e(LOG_TAG, errorMessage);
- }
+ final boolean isMainThread = Looper.getMainLooper().isCurrentThread();
+ final String errorMessage = "onDisplayRemoved must be called from the main thread";
+ if (Build.IS_USERDEBUG || Build.IS_ENG) {
+ Preconditions.checkArgument(isMainThread, errorMessage);
+ } else if (!isMainThread) {
+ Slog.e(LOG_TAG, errorMessage);
}
synchronized (mLock) {
if (!removeDisplayFromList(displayId)) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index b90a66a..fb28055 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -220,7 +220,7 @@
@Override
public void onServiceConnected(ComponentName componentName, IBinder service) {
AccessibilityUserState userState = mUserStateWeakReference.get();
- if (userState != null && Flags.addWindowTokenWithoutLock()) {
+ if (userState != null) {
addWindowTokensForAllDisplays();
}
synchronized (mLock) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 063eafe..9a1d379 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -66,6 +66,8 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
/**
* Class that hold states and settings per user and share between
@@ -104,6 +106,15 @@
final ArraySet<String> mAccessibilityButtonTargets = new ArraySet<>();
private final ArraySet<String> mAccessibilityQsTargets = new ArraySet<>();
+ /**
+ * The QuickSettings tiles in the QS Panel. This can be different from
+ * {@link #mAccessibilityQsTargets} in that {@link #mA11yTilesInQsPanel} stores the
+ * TileService's or the a11y framework tile component names (e.g.
+ * {@link AccessibilityShortcutController#COLOR_INVERSION_TILE_COMPONENT_NAME}) instead of the
+ * A11y Feature's component names.
+ */
+ private final ArraySet<ComponentName> mA11yTilesInQsPanel = new ArraySet<>();
+
private final ServiceInfoChangeListener mServiceInfoChangeListener;
private ComponentName mServiceChangingSoftKeyboardMode;
@@ -232,9 +243,6 @@
void addServiceLocked(AccessibilityServiceConnection serviceConnection) {
if (!mBoundServices.contains(serviceConnection)) {
- if (!Flags.addWindowTokenWithoutLock()) {
- serviceConnection.addWindowTokensForAllDisplays();
- }
mBoundServices.add(serviceConnection);
mComponentNameToServiceMap.put(serviceConnection.getComponentName(), serviceConnection);
mServiceInfoChangeListener.onServiceInfoChangedLocked(this);
@@ -566,7 +574,9 @@
pw.println("}");
pw.append(" button target:{").append(mTargetAssignedToAccessibilityButton);
pw.println("}");
- pw.append(" qs shortcut targets:" + mAccessibilityQsTargets);
+ pw.append(" qs shortcut targets:").append(mAccessibilityQsTargets.toString());
+ pw.println();
+ pw.append(" a11y tiles in QS panel:").append(mA11yTilesInQsPanel.toString());
pw.println();
pw.append(" Bound services:{");
final int serviceCount = mBoundServices.size();
@@ -1100,10 +1110,46 @@
return new ArraySet<>(mAccessibilityQsTargets);
}
+ public void updateA11yTilesInQsPanelLocked(Set<ComponentName> componentNames) {
+ mA11yTilesInQsPanel.clear();
+ mA11yTilesInQsPanel.addAll(componentNames);
+ }
+
+ /**
+ * Returns a copy of the a11y tiles that are in the QuickSettings panel
+ */
+ public ArraySet<ComponentName> getA11yQsTilesInQsPanel() {
+ return new ArraySet<>(mA11yTilesInQsPanel);
+ }
+
+ /**
+ * Returns a map of AccessibilityService or AccessibilityShortcut to its provided TileService
+ */
public Map<ComponentName, ComponentName> getA11yFeatureToTileService() {
Map<ComponentName, ComponentName> featureToTileServiceMap = new ArrayMap<>();
featureToTileServiceMap.putAll(mA11yServiceToTileService);
featureToTileServiceMap.putAll(mA11yActivityToTileService);
return featureToTileServiceMap;
}
+
+ /**
+ * Returns a map of TileService's componentName to the AccessibilityServiceInfo it ties to.
+ */
+ public Map<ComponentName, AccessibilityServiceInfo> getTileServiceToA11yServiceInfoMapLocked() {
+ Map<ComponentName, AccessibilityServiceInfo> tileServiceToA11yServiceInfoMap =
+ new ArrayMap<>();
+ Map<ComponentName, AccessibilityServiceInfo> a11yServiceToServiceInfoMap =
+ mInstalledServices.stream().collect(
+ Collectors.toMap(
+ AccessibilityServiceInfo::getComponentName,
+ Function.identity()));
+ for (Map.Entry<ComponentName, ComponentName> serviceToTile :
+ mA11yServiceToTileService.entrySet()) {
+ if (a11yServiceToServiceInfoMap.containsKey(serviceToTile.getKey())) {
+ tileServiceToA11yServiceInfoMap.put(serviceToTile.getValue(),
+ a11yServiceToServiceInfoMap.get(serviceToTile.getKey()));
+ }
+ }
+ return tileServiceToA11yServiceInfoMap;
+ }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index f69104d..aad9e24 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -136,9 +136,6 @@
return;
}
- if (!Flags.addWindowTokenWithoutLock()) {
- mUiAutomationService.addWindowTokensForAllDisplays();
- }
// UiAutomationService#connectServiceUnknownThread posts to a handler
// so this call should return immediately.
mUiAutomationService.connectServiceUnknownThread();
@@ -286,9 +283,7 @@
// If the serviceInterface is null, the UiAutomation has been shut down on
// another thread.
if (serviceInterface != null) {
- if (Flags.addWindowTokenWithoutLock()) {
- mUiAutomationService.addWindowTokensForAllDisplays();
- }
+ mUiAutomationService.addWindowTokensForAllDisplays();
if (mTrace.isA11yTracingEnabledForTypes(
AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) {
mTrace.logTrace("UiAutomationService.connectServiceUnknownThread",
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index 3645c40..9c84b12 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -545,6 +545,25 @@
});
}
+ /**
+ * Set views_fillable_total_count as long as mEventInternal presents.
+ */
+ public void maybeSetViewFillableCounts(int totalFillableCount) {
+ mEventInternal.ifPresent(event -> {
+ event.mViewFillableTotalCount = totalFillableCount;
+ });
+ }
+
+ /**
+ * Set views_filled_failure_count using failure count as long as mEventInternal
+ * presents.
+ */
+ public void maybeSetViewFillFailureCounts(int failureCount) {
+ mEventInternal.ifPresent(event -> {
+ event.mViewFillFailureCount = failureCount;
+ });
+ }
+
public void logAndEndEvent() {
if (!mEventInternal.isPresent()) {
Slog.w(TAG, "Shouldn't be logging AutofillPresentationEventReported again for same "
@@ -587,7 +606,9 @@
+ " mFieldClassificationRequestId=" + event.mFieldClassificationRequestId
+ " mAppPackageUid=" + mCallingAppUid
+ " mIsCredentialRequest=" + event.mIsCredentialRequest
- + " mWebviewRequestedCredential=" + event.mWebviewRequestedCredential);
+ + " mWebviewRequestedCredential=" + event.mWebviewRequestedCredential
+ + " mViewFillableTotalCount=" + event.mViewFillableTotalCount
+ + " mViewFillFailureCount=" + event.mViewFillFailureCount);
}
// TODO(b/234185326): Distinguish empty responses from other no presentation reasons.
@@ -628,7 +649,9 @@
event.mFieldClassificationRequestId,
mCallingAppUid,
event.mIsCredentialRequest,
- event.mWebviewRequestedCredential);
+ event.mWebviewRequestedCredential,
+ event.mViewFillableTotalCount,
+ event.mViewFillFailureCount);
mEventInternal = Optional.empty();
}
@@ -664,6 +687,8 @@
int mFieldClassificationRequestId = -1;
boolean mIsCredentialRequest = false;
boolean mWebviewRequestedCredential = false;
+ int mViewFillableTotalCount = -1;
+ int mViewFillFailureCount = -1;
PresentationStatsEventInternal() {}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index a55b8d0..d006cf6 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -170,8 +170,8 @@
import android.util.SparseArray;
import android.util.TimeUtils;
import android.view.KeyEvent;
-import android.view.autofill.AutofillId;
import android.view.autofill.AutofillFeatureFlags;
+import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillManager.AutofillCommitReason;
import android.view.autofill.AutofillManager.SmartSuggestionMode;
@@ -597,6 +597,17 @@
private boolean mIgnoreViewStateResetToEmpty;
+ /*
+ * Id of the previous view that was entered. Once set, it would only be replaced by non-null
+ * view ids.
+ * When a user focuses on a field, autofill request is sent. When the keyboard pops up, or the
+ * autofill dialog shows up, this field loses focus. After selecting a suggestion, focus goes
+ * back to the same field. This field allows to ignore focus loss when autofill dialog comes up.
+ * TODO(b/319872477): Note that there maybe some cases where we incorrectly detect focus loss.
+ */
+ @GuardedBy("mLock")
+ private @Nullable AutofillId mPreviousNonNullEnteredViewId;
+
void onSwitchInputMethodLocked() {
// One caveat is that for the case where the focus is on a field for which regular autofill
// returns null, and augmented autofill is triggered, and then the user switches the input
@@ -4390,6 +4401,7 @@
case ACTION_START_SESSION:
// View is triggering autofill.
mCurrentViewId = viewState.id;
+ mPreviousNonNullEnteredViewId = viewState.id;
viewState.update(value, virtualBounds, flags);
startNewEventForPresentationStatsEventLogger();
mPresentationStatsEventLogger.maybeSetIsNewRequest(true);
@@ -4459,6 +4471,14 @@
if (value != null) {
viewState.setCurrentValue(value);
}
+ // isSameViewEntered has some limitations, where it isn't considered same view when
+ // autofill suggestions pop up, user selects, and the focus lands back on the view.
+ // isSameViewAgain tries to overcome that situation.
+ final boolean isSameViewAgain = isSameViewEntered
+ || Objects.equals(mCurrentViewId, mPreviousNonNullEnteredViewId);
+ if (mCurrentViewId != null) {
+ mPreviousNonNullEnteredViewId = mCurrentViewId;
+ }
boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0;
if (shouldRequestSecondaryProvider(flags)) {
if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(
@@ -4510,7 +4530,8 @@
// With Fill Dialog, request starts prior to view getting entered. So, we can't end
// the event at this moment, otherwise we will be wrongly attributing fill dialog
// event as concluded.
- if (!wasPreviouslyFillDialog) {
+ if (!wasPreviouslyFillDialog && !isSameViewAgain) {
+ // TODO(b/319872477): Re-consider this logic below
mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED);
mPresentationStatsEventLogger.logAndEndEvent();
@@ -4588,10 +4609,10 @@
mCurrentViewId = null;
}
-
+ // It's not necessary that there's no more presentation for this view. It could
+ // be that the user chose some suggestion, in which case, view exits.
mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED);
- mPresentationStatsEventLogger.logAndEndEvent();
}
break;
default:
@@ -5327,6 +5348,9 @@
*/
@GuardedBy("mLock")
void setAutofillFailureLocked(@NonNull List<AutofillId> ids) {
+ if (sVerbose && !ids.isEmpty()) {
+ Slog.v(TAG, "Total views that failed to populate: " + ids.size());
+ }
for (int i = 0; i < ids.size(); i++) {
final AutofillId id = ids.get(i);
final ViewState viewState = mViewStates.get(id);
@@ -5341,6 +5365,8 @@
Slog.v(TAG, "Changed state of " + id + " to " + viewState.getStateAsString());
}
}
+ mPresentationStatsEventLogger.maybeSetViewFillFailureCounts(ids.size());
+ mPresentationStatsEventLogger.logAndEndEvent();
}
@GuardedBy("mLock")
@@ -6526,8 +6552,11 @@
if (waitingDatasetAuth) {
mUi.hideFillUi(this);
}
+ if (sVerbose) {
+ Slog.v(TAG, "Total views to be autofilled: " + ids.size());
+ }
+ mPresentationStatsEventLogger.maybeSetViewFillableCounts(ids.size());
if (sDebug) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
-
mClient.autofill(id, ids, values, hideHighlight);
if (dataset.getId() != null) {
if (mSelectedDatasetIds == null) {
diff --git a/services/contextualsearch/Android.bp b/services/contextualsearch/Android.bp
new file mode 100644
index 0000000..b4dd20e
--- /dev/null
+++ b/services/contextualsearch/Android.bp
@@ -0,0 +1,22 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "services.contextualsearch-sources",
+ srcs: ["java/**/*.java"],
+ path: "java",
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+ name: "services.contextualsearch",
+ defaults: ["platform_service_defaults"],
+ srcs: [":services.contextualsearch-sources"],
+ libs: ["services.core"],
+}
diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
new file mode 100644
index 0000000..b28bc1f
--- /dev/null
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
@@ -0,0 +1,315 @@
+/*
+ * 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.contextualsearch;
+
+import static android.Manifest.permission.ACCESS_CONTEXTUAL_SEARCH;
+import static android.content.Context.CONTEXTUAL_SEARCH_SERVICE;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
+import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
+import static android.content.pm.PackageManager.MATCH_FACTORY_ONLY;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.app.ActivityOptions;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.app.contextualsearch.ContextualSearchManager;
+import android.app.contextualsearch.ContextualSearchState;
+import android.app.contextualsearch.IContextualSearchCallback;
+import android.app.contextualsearch.IContextualSearchManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelableException;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.util.Log;
+import android.util.Slog;
+import android.window.ScreenCapture;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.wm.ActivityAssistInfo;
+import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
+
+import java.io.FileDescriptor;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class ContextualSearchManagerService extends SystemService {
+
+ private static final int MSG_RESET_TEMPORARY_PACKAGE = 0;
+ private static final int MAX_TEMP_PACKAGE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
+ private final Context mContext;
+ private final ActivityTaskManagerInternal mAtmInternal;
+ private final WindowManagerInternal mWmInternal;
+ private final DevicePolicyManagerInternal mDpmInternal;
+
+ private Handler mTemporaryHandler;
+
+ @GuardedBy("this")
+ private String mTemporaryPackage = null;
+ private static final String TAG = ContextualSearchManagerService.class.getSimpleName();
+
+ public ContextualSearchManagerService(@NonNull Context context) {
+ super(context);
+ if (DEBUG_USER) Log.d(TAG, "ContextualSearchManagerService created");
+ mContext = context;
+ mAtmInternal = Objects.requireNonNull(
+ LocalServices.getService(ActivityTaskManagerInternal.class));
+ mWmInternal = Objects.requireNonNull(LocalServices.getService(WindowManagerInternal.class));
+ mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(CONTEXTUAL_SEARCH_SERVICE, new ContextualSearchManagerStub());
+ }
+
+ void resetTemporaryPackage() {
+ synchronized (this) {
+ enforceOverridingPermission("resetTemporaryPackage");
+ if (mTemporaryHandler != null) {
+ mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_PACKAGE);
+ mTemporaryHandler = null;
+ }
+ if (DEBUG_USER) Log.d(TAG, "mTemporaryPackage reset.");
+ mTemporaryPackage = null;
+ }
+ }
+
+ void setTemporaryPackage(@NonNull String temporaryPackage, int durationMs) {
+ synchronized (this) {
+ enforceOverridingPermission("setTemporaryPackage");
+ final int maxDurationMs = MAX_TEMP_PACKAGE_DURATION_MS;
+ if (durationMs > maxDurationMs) {
+ throw new IllegalArgumentException(
+ "Max duration is " + maxDurationMs + " (called with " + durationMs + ")");
+ }
+ if (mTemporaryHandler == null) {
+ mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_RESET_TEMPORARY_PACKAGE) {
+ synchronized (this) {
+ resetTemporaryPackage();
+ }
+ } else {
+ Slog.wtf(TAG, "invalid handler msg: " + msg);
+ }
+ }
+ };
+ } else {
+ mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_PACKAGE);
+ }
+ mTemporaryPackage = temporaryPackage;
+ mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_PACKAGE, durationMs);
+ if (DEBUG_USER) Log.d(TAG, "mTemporaryPackage set to " + mTemporaryPackage);
+ }
+ }
+
+ private Intent getResolvedLaunchIntent() {
+ synchronized (this) {
+ // If mTemporaryPackage is not null, use it to get the ContextualSearch intent.
+ String csPkgName = mTemporaryPackage != null ? mTemporaryPackage : mContext
+ .getResources().getString(R.string.config_defaultContextualSearchPackageName);
+ if (csPkgName.isEmpty()) {
+ // Return null if csPackageName is not specified.
+ return null;
+ }
+ Intent launchIntent = new Intent(
+ ContextualSearchManager.ACTION_LAUNCH_CONTEXTUAL_SEARCH);
+ launchIntent.setPackage(csPkgName);
+ ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivity(
+ launchIntent, MATCH_FACTORY_ONLY);
+ if (resolveInfo == null) {
+ return null;
+ }
+ ComponentName componentName = resolveInfo.getComponentInfo().getComponentName();
+ if (componentName == null) {
+ return null;
+ }
+ launchIntent.setComponent(componentName);
+ return launchIntent;
+ }
+ }
+
+ private Intent getContextualSearchIntent(int entrypoint, IBinder mToken) {
+ final Intent launchIntent = getResolvedLaunchIntent();
+ if (launchIntent == null) {
+ return null;
+ }
+
+ if (DEBUG_USER) Log.d(TAG, "Launch component: " + launchIntent.getComponent());
+ launchIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_ANIMATION
+ | FLAG_ACTIVITY_NO_USER_ACTION);
+ launchIntent.putExtra(ContextualSearchManager.EXTRA_ENTRYPOINT, entrypoint);
+ launchIntent.putExtra(ContextualSearchManager.EXTRA_TOKEN, mToken);
+ boolean isAssistDataAllowed = mAtmInternal.isAssistDataAllowed();
+ final List<ActivityAssistInfo> records = mAtmInternal.getTopVisibleActivities();
+ ArrayList<String> visiblePackageNames = new ArrayList<>();
+ boolean isManagedProfileVisible = false;
+ for (ActivityAssistInfo record : records) {
+ // Add the package name to the list only if assist data is allowed.
+ if (isAssistDataAllowed) {
+ visiblePackageNames.add(record.getComponentName().getPackageName());
+ }
+ if (mDpmInternal != null
+ && mDpmInternal.isUserOrganizationManaged(record.getUserId())) {
+ isManagedProfileVisible = true;
+ }
+ }
+ final ScreenCapture.ScreenshotHardwareBuffer shb;
+ if (mWmInternal != null) {
+ shb = mWmInternal.takeAssistScreenshot();
+ } else {
+ shb = null;
+ }
+ final Bitmap bm = shb != null ? shb.asBitmap() : null;
+ // Now that everything is fetched, putting it in the launchIntent.
+ if (bm != null) {
+ launchIntent.putExtra(ContextualSearchManager.EXTRA_FLAG_SECURE_FOUND,
+ shb.containsSecureLayers());
+ // Only put the screenshot if assist data is allowed
+ if (isAssistDataAllowed) {
+ launchIntent.putExtra(ContextualSearchManager.EXTRA_SCREENSHOT, bm.asShared());
+ }
+ }
+ launchIntent.putExtra(ContextualSearchManager.EXTRA_IS_MANAGED_PROFILE_VISIBLE,
+ isManagedProfileVisible);
+ // Only put the list of visible package names if assist data is allowed
+ if (isAssistDataAllowed) {
+ launchIntent.putExtra(ContextualSearchManager.EXTRA_VISIBLE_PACKAGE_NAMES,
+ visiblePackageNames);
+ }
+ return launchIntent;
+ }
+
+ @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS)
+ private int invokeContextualSearchIntent(Intent launchIntent) {
+ // Contextual search starts with a frozen screen - so we launch without
+ // any system animations or starting window.
+ final ActivityOptions opts = ActivityOptions.makeCustomTaskAnimation(mContext,
+ /* enterResId= */ 0, /* exitResId= */ 0, null, null, null);
+ opts.setDisableStartingWindow(true);
+ return mAtmInternal.startActivityWithScreenshot(launchIntent,
+ mContext.getPackageName(), Binder.getCallingUid(), Binder.getCallingPid(), null,
+ opts.toBundle(), Binder.getCallingUserHandle().getIdentifier());
+ }
+
+ private void enforcePermission(@NonNull final String func) {
+ Context ctx = getContext();
+ if (!(ctx.checkCallingPermission(ACCESS_CONTEXTUAL_SEARCH) == PERMISSION_GRANTED
+ || isCallerTemporary())) {
+ String msg = "Permission Denial: Cannot call " + func + " from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid();
+ throw new SecurityException(msg);
+ }
+ }
+
+ private void enforceOverridingPermission(@NonNull final String func) {
+ if (!(Binder.getCallingUid() == Process.SHELL_UID
+ || Binder.getCallingUid() == Process.ROOT_UID
+ || Binder.getCallingUid() == Process.SYSTEM_UID)) {
+ String msg = "Permission Denial: Cannot override Contextual Search. Called " + func
+ + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid();
+ throw new SecurityException(msg);
+ }
+ }
+
+ private boolean isCallerTemporary() {
+ synchronized (this) {
+ return mTemporaryPackage != null
+ && mTemporaryPackage.equals(
+ getContext().getPackageManager().getNameForUid(Binder.getCallingUid()));
+ }
+ }
+
+ private class ContextualSearchManagerStub extends IContextualSearchManager.Stub {
+ private @Nullable IBinder mToken;
+
+ @Override
+ public void startContextualSearch(int entrypoint) {
+ synchronized (this) {
+ if (DEBUG_USER) Log.d(TAG, "startContextualSearch");
+ enforcePermission("startContextualSearch");
+ mToken = new Binder();
+ // We get the launch intent with the system server's identity because the system
+ // server has READ_FRAME_BUFFER permission to get the screenshot and because only
+ // the system server can invoke non-exported activities.
+ Binder.withCleanCallingIdentity(() -> {
+ Intent launchIntent = getContextualSearchIntent(entrypoint, mToken);
+ if (launchIntent != null) {
+ int result = invokeContextualSearchIntent(launchIntent);
+ if (DEBUG_USER) Log.d(TAG, "Launch result: " + result);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void getContextualSearchState(
+ @NonNull IBinder token,
+ @NonNull IContextualSearchCallback callback) {
+ if (DEBUG_USER) {
+ Log.i(TAG, "getContextualSearchState token: " + token + ", callback: " + callback);
+ }
+ if (mToken == null || !mToken.equals(token)) {
+ if (DEBUG_USER) {
+ Log.e(TAG, "getContextualSearchState: invalid token, returning error");
+ }
+ try {
+ callback.onError(
+ new ParcelableException(new IllegalArgumentException("Invalid token")));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not invoke onError callback", e);
+ }
+ return;
+ }
+ mToken = null;
+ // Process data request
+ try {
+ callback.onResult(new ContextualSearchState(null, null, Bundle.EMPTY));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not invoke onResult callback", e);
+ }
+ }
+
+ public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err, @NonNull String[] args,
+ @Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver) {
+ new ContextualSearchManagerShellCommand(ContextualSearchManagerService.this)
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+ }
+}
diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerShellCommand.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerShellCommand.java
new file mode 100644
index 0000000..5777e1d
--- /dev/null
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerShellCommand.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 com.android.server.contextualsearch;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+public class ContextualSearchManagerShellCommand extends ShellCommand {
+
+ private final ContextualSearchManagerService mService;
+
+ ContextualSearchManagerShellCommand(@NonNull ContextualSearchManagerService service) {
+ mService = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ final PrintWriter pw = getOutPrintWriter();
+ switch (cmd) {
+ case "set": {
+ final String what = getNextArgRequired();
+ switch (what) {
+ case "temporary-package": {
+ String packageName = getNextArg();
+ if (packageName == null) {
+ mService.resetTemporaryPackage();
+ pw.println("ContextualSearchManagerService reset.");
+ return 0;
+ }
+ final int duration = Integer.parseInt(getNextArgRequired());
+ mService.setTemporaryPackage(packageName, duration);
+ pw.println("ContextualSearchManagerService temporarily set to "
+ + packageName + " for " + duration + "ms");
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ try (PrintWriter pw = getOutPrintWriter()) {
+ pw.println("ContextualSearchService commands:");
+ pw.println(" help");
+ pw.println(" Prints this help text.");
+ pw.println("");
+ pw.println(" set temporary-package [PACKAGE_NAME DURATION]");
+ pw.println(" Temporarily (for DURATION ms) changes the Contextual Search "
+ + "implementation.");
+ pw.println(" To reset, call without any arguments.");
+ pw.println("");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 5298846..133a77d 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -204,6 +204,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
import android.service.voice.HotwordDetectionService;
import android.service.voice.VisualQueryDetectionService;
import android.service.wearable.WearableSensingService;
@@ -4518,13 +4519,14 @@
}
// TODO(b/265746493): Special case for HotwordDetectionService,
- // VisualQueryDetectionService and WearableSensingService.
+ // VisualQueryDetectionService, WearableSensingService and OnDeviceSandboxedInferenceService
// Need a cleaner way to append this seInfo.
private String generateAdditionalSeInfoFromService(Intent service) {
if (service != null && service.getAction() != null
&& (service.getAction().equals(HotwordDetectionService.SERVICE_INTERFACE)
|| service.getAction().equals(VisualQueryDetectionService.SERVICE_INTERFACE)
- || service.getAction().equals(WearableSensingService.SERVICE_INTERFACE))) {
+ || service.getAction().equals(WearableSensingService.SERVICE_INTERFACE)
+ || service.getAction().equals(OnDeviceSandboxedInferenceService.SERVICE_INTERFACE))) {
return ":isolatedComputeApp";
}
return "";
@@ -4656,6 +4658,8 @@
INVALID_UID /* sdkSandboxClientAppUid */,
null /* sdkSandboxClientAppPackage */,
false /* inSharedIsolatedProcess */);
+ r.foregroundId = fgsDelegateOptions.mClientNotificationId;
+ r.foregroundNoti = fgsDelegateOptions.mClientNotification;
res.setService(r);
smap.mServicesByInstanceName.put(cn, r);
smap.mServicesByIntent.put(filter, r);
@@ -8169,7 +8173,7 @@
* @param targetProcess the process of the service to start.
* @return {@link ReasonCode}
*/
- private @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage,
+ @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage,
int callingPid, int callingUid, @Nullable ProcessRecord targetProcess,
BackgroundStartPrivileges backgroundStartPrivileges) {
int ret = REASON_DENIED;
@@ -9044,6 +9048,10 @@
});
}
signalForegroundServiceObserversLocked(r);
+ if (r.foregroundId != 0 && r.foregroundNoti != null) {
+ r.foregroundNoti.flags |= Notification.FLAG_FOREGROUND_SERVICE;
+ r.postNotification(true);
+ }
return true;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index ff83797..272e84b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -51,6 +51,7 @@
import android.util.ArraySet;
import android.util.KeyValueListParser;
import android.util.Slog;
+import android.util.SparseBooleanArray;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -436,6 +437,18 @@
private static final String KEY_MAX_SERVICE_CONNECTIONS_PER_PROCESS =
"max_service_connections_per_process";
+ private static final String KEY_PROC_STATE_DEBUG_UIDS = "proc_state_debug_uids";
+
+ /**
+ * UIDs we want to print detailed info in OomAdjuster.
+ * It's only used for debugging, and it's almost never updated, so we just create a new
+ * array when it's changed to avoid synchronization.
+ */
+ volatile SparseBooleanArray mProcStateDebugUids = new SparseBooleanArray(0);
+ volatile boolean mEnableProcStateStacktrace = false;
+ volatile int mProcStateDebugSetProcStateDelay = 0;
+ volatile int mProcStateDebugSetUidStateDelay = 0;
+
// Maximum number of cached processes we will allow.
public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
@@ -1339,6 +1352,9 @@
case KEY_PSS_TO_RSS_THRESHOLD_MODIFIER:
updatePssToRssThresholdModifier();
break;
+ case KEY_PROC_STATE_DEBUG_UIDS:
+ updateProcStateDebugUids();
+ break;
default:
updateFGSPermissionEnforcementFlagsIfNecessary(name);
break;
@@ -2039,6 +2055,76 @@
DEFAULT_MAX_PREVIOUS_TIME);
}
+ private void updateProcStateDebugUids() {
+ final String val = DeviceConfig.getString(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_PROC_STATE_DEBUG_UIDS,
+ "").trim();
+
+ // Parse KEY_PROC_STATE_DEBUG_UIDS as comma-separated values. Each values can be:
+ // Number: Enable debugging on the given UID.
+ // "stack": Enable stack trace when updating proc/uid-states.s
+ // "u" + delay-ms: Enable sleep when updating uid-state
+ // "p" + delay-ms: Enable sleep when updating procstate
+ //
+ // Example:
+ // device_config put activity_manager proc_state_debug_uids '10177,10202,stack,p500,u100'
+ // means:
+ // - Monitor UID 10177 and 10202
+ // - Also enable stack trace
+ // - Sleep 500 ms when updating the procstate.
+ // - Sleep 100 ms when updating the UID state.
+
+ mEnableProcStateStacktrace = false;
+ mProcStateDebugSetProcStateDelay = 0;
+ mProcStateDebugSetUidStateDelay = 0;
+ if (val.length() == 0) {
+ mProcStateDebugUids = new SparseBooleanArray(0);
+ return;
+ }
+ final String[] uids = val.split(",");
+
+ final SparseBooleanArray newArray = new SparseBooleanArray(0);
+
+ for (String token : uids) {
+ if (token.length() == 0) {
+ continue;
+ }
+ // "stack" -> enable stacktrace.
+ if ("stack".equals(token)) {
+ mEnableProcStateStacktrace = true;
+ continue;
+ }
+ boolean isUid = true;
+ char prefix = token.charAt(0);
+ if ('a' <= prefix && prefix <= 'z') {
+ // If the token starts with an alphabet, it's not a UID.
+ isUid = false;
+ token = token.substring(1);
+ }
+
+ int value = -1;
+ try {
+ value = Integer.parseInt(token.trim());
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "Invalid number " + token + " in " + val);
+ continue;
+ }
+ if (isUid) {
+ newArray.put(value, true);
+ } else if (prefix == 'p') {
+ // Enable delay in set-proc-state
+ mProcStateDebugSetProcStateDelay = value;
+ } else if (prefix == 'u') {
+ // Enable delay in set-uid-state
+ mProcStateDebugSetUidStateDelay = value;
+ } else {
+ Slog.w(TAG, "Invalid prefix " + prefix + " in " + val);
+ }
+ }
+ mProcStateDebugUids = newArray;
+ }
+
private void updateMinAssocLogDuration() {
MIN_ASSOC_LOG_DURATION = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_MIN_ASSOC_LOG_DURATION,
@@ -2178,6 +2264,28 @@
mDefaultPssToRssThresholdModifier);
}
+ boolean shouldDebugUidForProcState(int uid) {
+ SparseBooleanArray ar = mProcStateDebugUids;
+ final var size = ar.size();
+ if (size == 0) { // Most common case.
+ return false;
+ }
+ // If the array is small (also common), avoid the binary search.
+ if (size <= 8) {
+ for (int i = 0; i < size; i++) {
+ if (ar.keyAt(i) == uid) {
+ return ar.valueAt(i);
+ }
+ }
+ return false;
+ }
+ return ar.get(uid, false);
+ }
+
+ boolean shouldEnableProcStateDebug() {
+ return mProcStateDebugUids.size() > 0;
+ }
+
@NeverCompile // Avoid size overhead of debugging code.
void dump(PrintWriter pw) {
pw.println("ACTIVITY MANAGER SETTINGS (dumpsys activity settings) "
@@ -2393,5 +2501,12 @@
pw.print(" OOMADJ_UPDATE_QUICK="); pw.println(OOMADJ_UPDATE_QUICK);
pw.print(" ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION=");
pw.println(mEnableWaitForFinishAttachApplication);
+
+ synchronized (mProcStateDebugUids) {
+ pw.print(" "); pw.print(KEY_PROC_STATE_DEBUG_UIDS);
+ pw.print("="); pw.println(mProcStateDebugUids);
+ pw.print(" uid-state-delay="); pw.println(mProcStateDebugSetUidStateDelay);
+ pw.print(" proc-state-delay="); pw.println(mProcStateDebugSetProcStateDelay);
+ }
}
}
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index 3e633cc..ddf1d5f 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -211,11 +211,17 @@
if (!mEnabled) {
return;
}
- if (!mInProgRecords.containsKey(id)) {
+ int index = mInProgRecords.indexOfKey(id);
+ if (index < 0) {
return;
}
- mInProgRecords.get(id).setStartupState(ApplicationStartInfo.STARTUP_STATE_ERROR);
- mInProgRecords.remove(id);
+ ApplicationStartInfo info = mInProgRecords.valueAt(index);
+ if (info == null) {
+ mInProgRecords.removeAt(index);
+ return;
+ }
+ info.setStartupState(ApplicationStartInfo.STARTUP_STATE_ERROR);
+ mInProgRecords.removeAt(index);
}
}
@@ -224,16 +230,24 @@
if (!mEnabled) {
return;
}
- if (!mInProgRecords.containsKey(id)) {
+ int index = mInProgRecords.indexOfKey(id);
+ if (index < 0) {
return;
}
- if (app != null) {
- ApplicationStartInfo info = mInProgRecords.get(id);
- info.setStartType((int) temperature);
- addBaseFieldsFromProcessRecord(info, app);
- mInProgRecords.put(id, addStartInfoLocked(info));
+ ApplicationStartInfo info = mInProgRecords.valueAt(index);
+ if (info == null || app == null) {
+ mInProgRecords.removeAt(index);
+ return;
+ }
+ info.setStartType((int) temperature);
+ addBaseFieldsFromProcessRecord(info, app);
+ ApplicationStartInfo newInfo = addStartInfoLocked(info);
+ if (newInfo == null) {
+ // newInfo can be null if records are added before load from storage is
+ // complete. In this case the newly added record will be lost.
+ mInProgRecords.removeAt(index);
} else {
- mInProgRecords.remove(id);
+ mInProgRecords.setValueAt(index, newInfo);
}
}
}
@@ -243,12 +257,17 @@
if (!mEnabled) {
return;
}
- if (!mInProgRecords.containsKey(id)) {
+ int index = mInProgRecords.indexOfKey(id);
+ if (index < 0) {
return;
}
- ApplicationStartInfo info = mInProgRecords.get(id);
+ ApplicationStartInfo info = mInProgRecords.valueAt(index);
+ if (info == null) {
+ mInProgRecords.removeAt(index);
+ return;
+ }
info.setStartupState(ApplicationStartInfo.STARTUP_STATE_ERROR);
- mInProgRecords.remove(id);
+ mInProgRecords.removeAt(index);
}
}
@@ -258,10 +277,15 @@
if (!mEnabled) {
return;
}
- if (!mInProgRecords.containsKey(id)) {
+ int index = mInProgRecords.indexOfKey(id);
+ if (index < 0) {
return;
}
- ApplicationStartInfo info = mInProgRecords.get(id);
+ ApplicationStartInfo info = mInProgRecords.valueAt(index);
+ if (info == null) {
+ mInProgRecords.removeAt(index);
+ return;
+ }
info.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN);
info.setLaunchMode(launchMode);
checkCompletenessAndCallback(info);
@@ -273,13 +297,18 @@
if (!mEnabled) {
return;
}
- if (!mInProgRecords.containsKey(id)) {
+ int index = mInProgRecords.indexOfKey(id);
+ if (index < 0) {
return;
}
- ApplicationStartInfo info = mInProgRecords.get(id);
+ ApplicationStartInfo info = mInProgRecords.valueAt(index);
+ if (info == null) {
+ mInProgRecords.removeAt(index);
+ return;
+ }
info.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN,
timestampNanos);
- mInProgRecords.remove(id);
+ mInProgRecords.removeAt(index);
}
}
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index 8038732..b142781 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -135,3 +135,9 @@
# Caller information to clear application data
30120 am_clear_app_data_caller (pid|1),(uid|1),(package|3)
+
+30111 am_uid_state_changed (UID|1|5),(Seq|1|5),(UidState|1|5),(OldUidState|1|5),(Capability|1|5),(OldCapability|1|5),(Flags|1|5),(reason|3)
+30112 am_proc_state_changed (UID|1|5),(PID|1|5),(Seq|1|5),(ProcState|1|5),(OldProcState|1|5),(OomAdj|1|5),(OldOomAdj|1|5),(reason|3)
+
+# "Misc" events. See OomAdjusterDebugLogger.java
+30113 am_oom_adj_misc (Event|1|5),(UID|1|5),(PID|1|5),(Seq|1|5),(Arg1|1|5),(Arg2|1|5),(reason|3)
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 7f6d62c..1a7629f 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -401,6 +401,14 @@
@GuardedBy("mService")
private boolean mPendingFullOomAdjUpdate = false;
+ /**
+ * Most recent reason string. We update it in sync with the trace.
+ */
+ @OomAdjReason
+ protected int mLastReason;
+
+ private final OomAdjusterDebugLogger mLogger;
+
/** Overrideable by a test */
@VisibleForTesting
protected boolean isChangeEnabled(@CachedCompatChangeId int cachedCompatChangeId,
@@ -433,6 +441,8 @@
mCachedAppOptimizer = new CachedAppOptimizer(mService);
mCacheOomRanker = new CacheOomRanker(service);
+ mLogger = new OomAdjusterDebugLogger(this, mService.mConstants);
+
mProcessGroupHandler = new Handler(adjusterThread.getLooper(), msg -> {
final int pid = msg.arg1;
final int group = msg.arg2;
@@ -636,6 +646,7 @@
protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
final ProcessRecord topApp = mService.getTopApp();
+ mLastReason = oomAdjReason;
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
mService.mOomAdjProfiler.oomAdjStarted();
mAdjSeq++;
@@ -916,6 +927,7 @@
protected void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
final ProcessRecord topApp = mService.getTopApp();
+ mLastReason = oomAdjReason;
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
mService.mOomAdjProfiler.oomAdjStarted();
@@ -958,6 +970,7 @@
}
}
+ mLastReason = oomAdjReason;
if (startProfiling) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
mService.mOomAdjProfiler.oomAdjStarted();
@@ -1491,6 +1504,7 @@
|| uidRec.isSetAllowListed() != uidRec.isCurAllowListed()
|| uidRec.getProcAdjChanged()) {
int uidChange = 0;
+ final boolean shouldLog = mLogger.shouldLog(uidRec.getUid());
if (DEBUG_UID_OBSERVERS) {
Slog.i(TAG_UID_OBSERVERS, "Changes in " + uidRec
+ ": proc state from " + uidRec.getSetProcState() + " to "
@@ -1511,14 +1525,21 @@
|| uidRec.isSetAllowListed()
|| uidRec.getLastBackgroundTime() == 0) {
uidRec.setLastBackgroundTime(nowElapsed);
+ if (shouldLog) {
+ mLogger.logSetLastBackgroundTime(uidRec.getUid(), nowElapsed);
+ }
if (mService.mDeterministicUidIdle
|| !mService.mHandler.hasMessages(IDLE_UIDS_MSG)) {
// Note: the background settle time is in elapsed realtime, while
// the handler time base is uptime. All this means is that we may
// stop background uids later than we had intended, but that only
// happens because the device was sleeping so we are okay anyway.
+ if (shouldLog) {
+ mLogger.logScheduleUidIdle1(uidRec.getUid(),
+ mConstants.BACKGROUND_SETTLE_TIME);
+ }
mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
- mConstants.BACKGROUND_SETTLE_TIME);
+ mConstants.BACKGROUND_SETTLE_TIME); // XXX
}
}
if (uidRec.isIdle() && !uidRec.isSetIdle()) {
@@ -1536,6 +1557,9 @@
}
uidRec.setLastBackgroundTime(0);
uidRec.setLastIdleTime(0);
+ if (shouldLog) {
+ mLogger.logClearLastBackgroundTime(uidRec.getUid());
+ }
}
final boolean wasCached = uidRec.getSetProcState()
> ActivityManager.PROCESS_STATE_RECEIVER;
@@ -1555,11 +1579,25 @@
if (uidRec.getProcAdjChanged()) {
uidChange |= UidRecord.CHANGE_PROCADJ;
}
+ int oldProcState = uidRec.getSetProcState();
+ int oldCapability = uidRec.getSetCapability();
uidRec.setSetProcState(uidRec.getCurProcState());
uidRec.setSetCapability(uidRec.getCurCapability());
uidRec.setSetAllowListed(uidRec.isCurAllowListed());
uidRec.setSetIdle(uidRec.isIdle());
uidRec.clearProcAdjChanged();
+ if (shouldLog
+ && ((uidRec.getSetProcState() != oldProcState)
+ || (uidRec.getSetCapability() != oldCapability))) {
+ int flags = 0;
+ if (uidRec.isSetAllowListed()) {
+ flags |= 1;
+ }
+ mLogger.logUidStateChanged(uidRec.getUid(),
+ uidRec.getSetProcState(), oldProcState,
+ uidRec.getSetCapability(), oldCapability,
+ flags);
+ }
if ((uidChange & UidRecord.CHANGE_PROCSTATE) != 0
|| (uidChange & UidRecord.CHANGE_CAPABILITY) != 0) {
mService.mAtmInternal.onUidProcStateChanged(
@@ -3300,6 +3338,7 @@
mCachedAppOptimizer.onOomAdjustChanged(state.getSetAdj(), state.getCurAdj(), app);
}
+ final int oldOomAdj = state.getSetAdj();
if (state.getCurAdj() != state.getSetAdj()) {
ProcessList.setOomAdj(app.getPid(), app.uid, state.getCurAdj());
if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.info.uid) {
@@ -3457,6 +3496,7 @@
mService.mAppProfiler.updateNextPssTimeLPf(
state.getCurProcState(), app.mProfile, now, forceUpdatePssTime);
}
+ int oldProcState = state.getSetProcState();
if (state.getSetProcState() != state.getCurProcState()) {
if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.uid) {
String msg = "Proc state change of " + app.processName
@@ -3556,6 +3596,11 @@
// Kick off the delayed checkup message if needed.
if (mService.mDeterministicUidIdle
|| !mService.mHandler.hasMessages(IDLE_UIDS_MSG)) {
+ if (mLogger.shouldLog(app.uid)) {
+ mLogger.logScheduleUidIdle2(
+ uidRec.getUid(), app.getPid(),
+ mConstants.mKillBgRestrictedAndCachedIdleSettleTimeMs);
+ }
mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
mConstants.mKillBgRestrictedAndCachedIdleSettleTimeMs);
}
@@ -3563,6 +3608,12 @@
}
state.setSetCached(state.isCached());
state.setSetNoKillOnBgRestrictedAndIdle(state.shouldNotKillOnBgRestrictedAndIdle());
+ if (((oldProcState != state.getSetProcState()) || (oldOomAdj != state.getSetAdj()))
+ && mLogger.shouldLog(app.uid)) {
+ mLogger.logProcStateChanged(app.uid, app.getPid(),
+ state.getSetProcState(), oldProcState,
+ state.getSetAdj(), oldOomAdj);
+ }
return success;
}
@@ -3704,6 +3755,7 @@
if (mService.mLocalPowerManager != null) {
mService.mLocalPowerManager.startUidChanges();
}
+ boolean shouldLogMisc = false;
for (int i = N - 1; i >= 0; i--) {
final UidRecord uidRec = mActiveUids.valueAt(i);
final long bgTime = uidRec.getLastBackgroundTime();
@@ -3721,6 +3773,9 @@
if (nextTime == 0 || nextTime > bgTime) {
nextTime = bgTime;
}
+ if (mLogger.shouldLog(uidRec.getUid())) {
+ shouldLogMisc = true;
+ }
}
}
}
@@ -3742,8 +3797,11 @@
}
}
if (nextTime > 0) {
- mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
- nextTime + mConstants.BACKGROUND_SETTLE_TIME - nowElapsed);
+ long delay = nextTime + mConstants.BACKGROUND_SETTLE_TIME - nowElapsed;
+ if (shouldLogMisc) {
+ mLogger.logScheduleUidIdle3(delay);
+ }
+ mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG, delay);
}
}
diff --git a/services/core/java/com/android/server/am/OomAdjusterDebugLogger.java b/services/core/java/com/android/server/am/OomAdjusterDebugLogger.java
new file mode 100644
index 0000000..1294a4d
--- /dev/null
+++ b/services/core/java/com/android/server/am/OomAdjusterDebugLogger.java
@@ -0,0 +1,110 @@
+/*
+ * 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.am;
+
+import android.app.StackTrace;
+import android.util.Slog;
+
+/**
+ * Helper for writing debug log about proc/uid state changes.
+ */
+class OomAdjusterDebugLogger {
+ // Use the "am_" tag to make it similar to event logs.
+ private static final String STACK_TRACE_TAG = "am_stack";
+
+ private final OomAdjuster mOomAdjuster;
+ private final ActivityManagerConstants mConstants;
+
+ private static final int MISC_SCHEDULE_IDLE_UIDS_MSG_1 = 1;
+ private static final int MISC_SCHEDULE_IDLE_UIDS_MSG_2 = 2;
+ private static final int MISC_SCHEDULE_IDLE_UIDS_MSG_3 = 3;
+
+ private static final int MISC_SET_LAST_BG_TIME = 10;
+ private static final int MISC_CLEAR_LAST_BG_TIME = 11;
+
+ OomAdjusterDebugLogger(OomAdjuster oomAdjuster, ActivityManagerConstants constants) {
+ mOomAdjuster = oomAdjuster;
+ mConstants = constants;
+ }
+
+ boolean shouldLog(int uid) {
+ return mConstants.shouldDebugUidForProcState(uid);
+ }
+
+ private void maybeLogStacktrace(String msg) {
+ if (!mConstants.mEnableProcStateStacktrace) {
+ return;
+ }
+ Slog.i(STACK_TRACE_TAG,
+ msg + ": " + OomAdjuster.oomAdjReasonToString(mOomAdjuster.mLastReason),
+ new StackTrace("Called here"));
+ }
+
+ private void maybeSleep(int millis) {
+ if (millis == 0) {
+ return;
+ }
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ void logUidStateChanged(int uid, int uidstate, int olduidstate,
+ int capability, int oldcapability, int flags) {
+ EventLogTags.writeAmUidStateChanged(
+ uid, mOomAdjuster.mAdjSeq, uidstate, olduidstate, capability, oldcapability, flags,
+ OomAdjuster.oomAdjReasonToString(mOomAdjuster.mLastReason));
+ maybeLogStacktrace("uidStateChanged");
+ maybeSleep(mConstants.mProcStateDebugSetUidStateDelay);
+ }
+
+ void logProcStateChanged(int uid, int pid, int procstate, int oldprocstate,
+ int oomadj, int oldoomadj) {
+ EventLogTags.writeAmProcStateChanged(
+ uid, pid, mOomAdjuster.mAdjSeq, procstate, oldprocstate, oomadj, oldoomadj,
+ OomAdjuster.oomAdjReasonToString(mOomAdjuster.mLastReason));
+ maybeLogStacktrace("procStateChanged");
+ maybeSleep(mConstants.mProcStateDebugSetProcStateDelay);
+ }
+
+ void logScheduleUidIdle1(int uid, long delay) {
+ EventLogTags.writeAmOomAdjMisc(MISC_SCHEDULE_IDLE_UIDS_MSG_1,
+ uid, 0, mOomAdjuster.mAdjSeq, (int) delay, 0, "");
+ }
+
+ void logScheduleUidIdle2(int uid, int pid, long delay) {
+ EventLogTags.writeAmOomAdjMisc(MISC_SCHEDULE_IDLE_UIDS_MSG_2,
+ uid, pid, mOomAdjuster.mAdjSeq, (int) delay, 0, "");
+ }
+
+ void logScheduleUidIdle3(long delay) {
+ EventLogTags.writeAmOomAdjMisc(MISC_SCHEDULE_IDLE_UIDS_MSG_3,
+ 0, 0, mOomAdjuster.mAdjSeq, (int) delay, 0, "");
+ }
+
+ void logSetLastBackgroundTime(int uid, long time) {
+ EventLogTags.writeAmOomAdjMisc(MISC_SET_LAST_BG_TIME,
+ uid, 0, mOomAdjuster.mAdjSeq, (int) time, 0,
+ OomAdjuster.oomAdjReasonToString(mOomAdjuster.mLastReason));
+ }
+
+ void logClearLastBackgroundTime(int uid) {
+ EventLogTags.writeAmOomAdjMisc(MISC_CLEAR_LAST_BG_TIME,
+ uid, 0, mOomAdjuster.mAdjSeq, 0, 0,
+ OomAdjuster.oomAdjReasonToString(mOomAdjuster.mLastReason));
+ }
+}
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 46bdfe8..5feac1f 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -741,6 +741,7 @@
mPendingProcessSet.clear();
mService.mAppProfiler.mHasPreviousProcess = mService.mAppProfiler.mHasHomeProcess = false;
+ mLastReason = oomAdjReason;
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
mService.mOomAdjProfiler.oomAdjStarted();
@@ -761,6 +762,7 @@
@GuardedBy("mService")
@Override
protected void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
+ mLastReason = oomAdjReason;
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
mService.mOomAdjProfiler.oomAdjStarted();
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 3d695bc..5cb8b95 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -674,14 +674,16 @@
return mScheduleServiceTimeoutPending;
}
- @GuardedBy("mService")
void onProcessUnfrozen() {
- scheduleServiceTimeoutIfNeededLocked();
+ synchronized (mService) {
+ scheduleServiceTimeoutIfNeededLocked();
+ }
}
- @GuardedBy("mService")
void onProcessFrozenCancelled() {
- scheduleServiceTimeoutIfNeededLocked();
+ synchronized (mService) {
+ scheduleServiceTimeoutIfNeededLocked();
+ }
}
@GuardedBy("mService")
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index b1823b4..e955b00 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -60,10 +60,3 @@
purpose: PURPOSE_BUGFIX
}
}
-
-flag {
- namespace: "backstage_power"
- name: "defer_outgoing_bcasts"
- description: "Defer outgoing broadcasts from processes in freezable state"
- bug: "327496592"
-}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index d4f04b5..e8c05c6 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7124,7 +7124,8 @@
switch (mPlatformType) {
case AudioSystem.PLATFORM_VOICE:
- if (isInCommunication()) {
+ if (isInCommunication()
+ || mAudioSystem.isStreamActive(AudioManager.STREAM_VOICE_CALL, 0)) {
if (mDeviceBroker.isBluetoothScoActive()) {
// Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO...");
return AudioSystem.STREAM_BLUETOOTH_SCO;
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 177c345..e2ae3de 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -76,7 +76,6 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
@@ -397,11 +396,10 @@
@NonNull
private DeviceStateInfo getDeviceStateInfoLocked() {
final List<DeviceState> supportedStates = getSupportedStatesLocked();
- final DeviceState baseState = mBaseState.orElse(null);
- final DeviceState currentState = mCommittedState.orElse(null);
+ final DeviceState baseState = mBaseState.orElse(INVALID_DEVICE_STATE);
+ final DeviceState currentState = mCommittedState.orElse(INVALID_DEVICE_STATE);
- return new DeviceStateInfo(supportedStates,
- baseState != null ? baseState : INVALID_DEVICE_STATE,
+ return new DeviceStateInfo(supportedStates, baseState,
createMergedDeviceState(currentState, baseState));
}
@@ -412,7 +410,7 @@
*/
private DeviceState createMergedDeviceState(@Nullable DeviceState committedState,
@Nullable DeviceState baseState) {
- if (committedState == null) {
+ if (committedState.equals(INVALID_DEVICE_STATE)) {
return INVALID_DEVICE_STATE;
}
@@ -420,8 +418,7 @@
committedState.getConfiguration().getSystemProperties();
Set<@DeviceState.DeviceStateProperties Integer> physicalProperties =
- baseState != null ? baseState.getConfiguration().getPhysicalProperties()
- : Collections.emptySet();
+ baseState.getConfiguration().getPhysicalProperties();
DeviceState.Configuration deviceStateConfiguration = new DeviceState.Configuration.Builder(
committedState.getIdentifier(), committedState.getName())
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 40b2f5a..10030b3 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -21,6 +21,7 @@
import android.os.IBinder;
import android.os.PowerManager;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.brightness.clamper.HdrClamper;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -30,8 +31,7 @@
class BrightnessRangeController {
private final HighBrightnessModeController mHbmController;
- private final NormalBrightnessModeController mNormalBrightnessModeController =
- new NormalBrightnessModeController();
+ private final NormalBrightnessModeController mNormalBrightnessModeController;
private final HdrClamper mHdrClamper;
@@ -45,17 +45,21 @@
Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig, Handler handler,
DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
this(hbmController, modeChangeCallback, displayDeviceConfig,
+ new NormalBrightnessModeController(),
new HdrClamper(modeChangeCallback::run, new Handler(handler.getLooper())), flags,
displayToken, info);
}
+ @VisibleForTesting
BrightnessRangeController(HighBrightnessModeController hbmController,
Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig,
+ NormalBrightnessModeController normalBrightnessModeController,
HdrClamper hdrClamper, DisplayManagerFlags flags, IBinder displayToken,
DisplayDeviceInfo info) {
mHbmController = hbmController;
mModeChangeCallback = modeChangeCallback;
mHdrClamper = hdrClamper;
+ mNormalBrightnessModeController = normalBrightnessModeController;
mUseHdrClamper = flags.isHdrClamperEnabled();
mUseNbmController = flags.isNbmControllerEnabled();
if (mUseNbmController) {
@@ -126,8 +130,11 @@
float getCurrentBrightnessMax() {
- if (mUseNbmController && mHbmController.getHighBrightnessMode()
- == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF) {
+ // nbmController might adjust maxBrightness only if device does not support HBM or
+ // hbm is currently not allowed
+ if (mUseNbmController
+ && (!mHbmController.deviceSupportsHbm()
+ || !mHbmController.isHbmCurrentlyAllowed())) {
return Math.min(mHbmController.getCurrentBrightnessMax(),
mNormalBrightnessModeController.getCurrentBrightnessMax());
}
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index a7748f4..0ebb2a3 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -158,8 +158,6 @@
@Nullable
public Point getDisplaySurfaceDefaultSizeLocked() {
DisplayDeviceInfo displayDeviceInfo = getDisplayDeviceInfoLocked();
- final boolean isRotated = mCurrentOrientation == ROTATION_90
- || mCurrentOrientation == ROTATION_270;
var width = displayDeviceInfo.width;
var height = displayDeviceInfo.height;
if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.yDpi > 0
@@ -170,7 +168,7 @@
width = (int) (width * displayDeviceInfo.yDpi / displayDeviceInfo.xDpi + 0.5);
}
}
- return isRotated ? new Point(height, width) : new Point(width, height);
+ return isRotatedLocked() ? new Point(height, width) : new Point(width, height);
}
/**
@@ -394,8 +392,7 @@
viewport.physicalFrame.setEmpty();
}
- boolean isRotated = (mCurrentOrientation == Surface.ROTATION_90
- || mCurrentOrientation == ROTATION_270);
+ final boolean isRotated = isRotatedLocked();
DisplayDeviceInfo info = getDisplayDeviceInfoLocked();
viewport.deviceWidth = isRotated ? info.height : info.width;
viewport.deviceHeight = isRotated ? info.width : info.height;
@@ -425,6 +422,13 @@
pw.println("mCurrentSurface=" + mCurrentSurface);
}
+ /**
+ * @return whether the orientation is {@link ROTATION_90} or {@link ROTATION_270}.
+ */
+ boolean isRotatedLocked() {
+ return mCurrentOrientation == ROTATION_90 || mCurrentOrientation == ROTATION_270;
+ }
+
private DisplayDeviceConfig loadDisplayDeviceConfig() {
return DisplayDeviceConfig.create(mContext, /* useConfigXml= */ false,
mDisplayAdapter.getFeatureFlags());
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 9b2dcc5..4116669 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -57,6 +57,9 @@
import com.android.server.display.config.HbmTiming;
import com.android.server.display.config.HdrBrightnessData;
import com.android.server.display.config.HighBrightnessMode;
+import com.android.server.display.config.IdleScreenRefreshRateTimeout;
+import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint;
+import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholds;
import com.android.server.display.config.IntegerArray;
import com.android.server.display.config.LuxThrottling;
import com.android.server.display.config.NitsMap;
@@ -553,6 +556,18 @@
* <minorVersion>0</minorVersion>
* </usiVersion>
* <screenBrightnessCapForWearBedtimeMode>0.1</screenBrightnessCapForWearBedtimeMode>
+ * <idleScreenRefreshRateTimeout>
+ * <luxThresholds>
+ * <point>
+ * <lux>6</lux>
+ * <timeout>1000</timeout>
+ * </point>
+ * <point>
+ * <lux>10</lux>
+ * <timeout>800</timeout>
+ * </point>
+ * </luxThresholds>
+ * </idleScreenRefreshRateTimeout>
* </displayConfiguration>
* }
* </pre>
@@ -843,6 +858,14 @@
private final Map<BrightnessLimitMapType, Map<Float, Float>>
mLuxThrottlingData = new HashMap<>();
+ /**
+ * The idle screen timeout configuration for switching to lower refresh rate
+ */
+ @NonNull
+ private List<IdleScreenRefreshRateTimeoutLuxThresholdPoint>
+ mIdleScreenRefreshRateTimeoutLuxThresholds = new ArrayList<>();
+
+
@Nullable
private HostUsiVersion mHostUsiVersion;
@@ -1999,6 +2022,7 @@
loadUsiVersion(config);
mHdrBrightnessData = HdrBrightnessData.loadConfig(config);
loadBrightnessCapForWearBedtimeMode(config);
+ loadIdleScreenRefreshRateTimeoutConfigs(config);
} else {
Slog.w(TAG, "DisplayDeviceConfig file is null");
}
@@ -2024,6 +2048,7 @@
loadAutoBrightnessAvailableFromConfigXml();
loadRefreshRateSetting(null);
loadBrightnessCapForWearBedtimeModeFromConfigXml();
+ loadIdleScreenRefreshRateTimeoutConfigs(null);
mLoadedFrom = "<config.xml>";
}
@@ -3326,6 +3351,47 @@
}
}
+ private void loadIdleScreenRefreshRateTimeoutConfigs(@Nullable DisplayConfiguration config) {
+ if (mFlags.isIdleScreenRefreshRateTimeoutEnabled()
+ && config != null && config.getIdleScreenRefreshRateTimeout() != null) {
+ validateIdleScreenRefreshRateTimeoutConfig(
+ config.getIdleScreenRefreshRateTimeout());
+ mIdleScreenRefreshRateTimeoutLuxThresholds = config
+ .getIdleScreenRefreshRateTimeout().getLuxThresholds().getPoint();
+ }
+ }
+
+ private void validateIdleScreenRefreshRateTimeoutConfig(
+ IdleScreenRefreshRateTimeout idleScreenRefreshRateTimeoutConfig) {
+ IdleScreenRefreshRateTimeoutLuxThresholds idleScreenRefreshRateTimeoutLuxThresholds =
+ idleScreenRefreshRateTimeoutConfig.getLuxThresholds();
+
+ if (idleScreenRefreshRateTimeoutLuxThresholds != null) {
+ int previousLux = -1;
+ // Validate that the lux values are in the increasing order
+ for (IdleScreenRefreshRateTimeoutLuxThresholdPoint point :
+ idleScreenRefreshRateTimeoutLuxThresholds.getPoint()) {
+ int newLux = point.getLux().intValue();
+ if (previousLux >= newLux) {
+ throw new RuntimeException("Lux values should be in ascending order in the"
+ + " idle screen refresh rate timeout config");
+ }
+ previousLux = newLux;
+ }
+ }
+ }
+
+ /**
+ * Gets the idle screen refresh rate timeout(in ms) configuration list. For each entry, the lux
+ * value represent the lower bound of the lux range, and the value of the lux in the next
+ * point(INF if not present) represents the upper bound for the corresponding timeout(in ms)
+ */
+ @NonNull
+ public List<IdleScreenRefreshRateTimeoutLuxThresholdPoint>
+ getIdleScreenRefreshRateTimeoutLuxThresholdPoint() {
+ return mIdleScreenRefreshRateTimeoutLuxThresholds;
+ }
+
/**
* Extracts a float array from the specified {@link TypedArray}.
*
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index ce7c224..84eebe8 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -22,6 +22,7 @@
import static android.Manifest.permission.CAPTURE_VIDEO_OUTPUT;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.Manifest.permission.MANAGE_DISPLAYS;
+import static android.Manifest.permission.RESTRICT_DISPLAY_MODES;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
import static android.hardware.display.DisplayManager.EventsMask;
@@ -75,6 +76,7 @@
import android.hardware.OverlayProperties;
import android.hardware.Sensor;
import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateManagerInternal;
import android.hardware.display.AmbientBrightnessDayStats;
@@ -4530,6 +4532,14 @@
disableConnectedDisplay_enforcePermission();
DisplayManagerService.this.enableConnectedDisplay(displayId, false);
}
+
+ @EnforcePermission(RESTRICT_DISPLAY_MODES)
+ @Override // Binder call
+ public void requestDisplayModes(IBinder token, int displayId, @Nullable int[] modeIds) {
+ requestDisplayModes_enforcePermission();
+ DisplayManagerService.this.mDisplayModeDirector.requestDisplayModes(
+ token, displayId, modeIds);
+ }
}
private static boolean isValidBrightness(float brightness) {
@@ -5034,30 +5044,22 @@
* Listens to changes in device state and reports the state to LogicalDisplayMapper.
*/
class DeviceStateListener implements DeviceStateManager.DeviceStateCallback {
- // Base state corresponds to the physical state of the device
- private int mBaseState = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
@Override
- public void onStateChanged(int deviceState) {
- boolean isDeviceStateOverrideActive = deviceState != mBaseState;
+ public void onDeviceStateChanged(DeviceState deviceState) {
synchronized (mSyncRoot) {
// Notify WindowManager that we are about to handle new device state, this should
// be sent before any work related to the device state in DisplayManager, so
// WindowManager could do implement that depends on the device state and display
// changes (serializes device state update and display change events)
Message msg = mHandler.obtainMessage(MSG_RECEIVED_DEVICE_STATE);
- msg.arg1 = deviceState;
+ msg.arg1 = deviceState.getIdentifier();
mHandler.sendMessage(msg);
mLogicalDisplayMapper
- .setDeviceStateLocked(deviceState, isDeviceStateOverrideActive);
+ .setDeviceStateLocked(deviceState.getIdentifier());
}
}
-
- @Override
- public void onBaseStateChanged(int state) {
- mBaseState = state;
- }
};
private class BrightnessPair {
diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
index ab7c503..a12d248 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
@@ -42,6 +42,9 @@
import com.android.server.display.notifications.DisplayNotificationManager;
import com.android.server.display.utils.DebugUtils;
+import java.util.HashSet;
+import java.util.Set;
+
/**
* Listens for Skin thermal sensor events, disables external displays if thermal status becomes
* equal or above {@link android.os.Temperature#THROTTLING_CRITICAL}, enables external displays if
@@ -106,6 +109,10 @@
private final ExternalDisplayStatsService mExternalDisplayStatsService;
@ThrottlingStatus
private volatile int mStatus = THROTTLING_NONE;
+ //@GuardedBy("mSyncRoot")
+ private boolean mIsBootCompleted;
+ //@GuardedBy("mSyncRoot")
+ private final Set<Integer> mDisplayIdsWaitingForBootCompletion = new HashSet<>();
ExternalDisplayPolicy(@NonNull final Injector injector) {
mInjector = injector;
@@ -121,6 +128,17 @@
* Starts listening for temperature changes.
*/
void onBootCompleted() {
+ synchronized (mSyncRoot) {
+ mIsBootCompleted = true;
+ for (var displayId : mDisplayIdsWaitingForBootCompletion) {
+ var logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayId);
+ if (logicalDisplay != null) {
+ handleExternalDisplayConnectedLocked(logicalDisplay);
+ }
+ }
+ mDisplayIdsWaitingForBootCompletion.clear();
+ }
+
if (!mFlags.isConnectedDisplayManagementEnabled()) {
if (DEBUG) {
Slog.d(TAG, "External display management is not enabled on your device:"
@@ -189,6 +207,11 @@
return;
}
+ if (!mIsBootCompleted) {
+ mDisplayIdsWaitingForBootCompletion.add(logicalDisplay.getDisplayIdLocked());
+ return;
+ }
+
mExternalDisplayStatsService.onDisplayConnected(logicalDisplay);
if ((Build.IS_ENG || Build.IS_USERDEBUG)
@@ -227,7 +250,12 @@
return;
}
- mExternalDisplayStatsService.onDisplayDisconnected(logicalDisplay.getDisplayIdLocked());
+ var displayId = logicalDisplay.getDisplayIdLocked();
+ if (mDisplayIdsWaitingForBootCompletion.remove(displayId)) {
+ return;
+ }
+
+ mExternalDisplayStatsService.onDisplayDisconnected(displayId);
}
/**
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index a9f78fd..47176fe 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -168,7 +168,7 @@
}
float getCurrentBrightnessMax() {
- if (!deviceSupportsHbm() || isCurrentlyAllowed()) {
+ if (!deviceSupportsHbm() || isHbmCurrentlyAllowed()) {
// Either the device doesn't support HBM, or HBM range is currently allowed (device
// it in a high-lux environment). In either case, return the highest brightness
// level supported by the device.
@@ -356,7 +356,7 @@
return event.getStartTimeMillis();
}
- private boolean isCurrentlyAllowed() {
+ boolean isHbmCurrentlyAllowed() {
// Returns true if HBM is allowed (above the ambient lux threshold) and there's still
// time within the current window for additional HBM usage. We return false if there is an
// HDR layer because we don't want the brightness MAX to change for HDR, which has its
@@ -369,7 +369,7 @@
&& !mIsBlockedByLowPowerMode);
}
- private boolean deviceSupportsHbm() {
+ boolean deviceSupportsHbm() {
return mHbmData != null && mHighBrightnessModeMetadata != null;
}
@@ -462,7 +462,7 @@
+ ", isOnlyAllowedToStayOn: " + isOnlyAllowedToStayOn
+ ", remainingAllowedTime: " + remainingTime
+ ", isLuxHigh: " + mIsInAllowedAmbientRange
- + ", isHBMCurrentlyAllowed: " + isCurrentlyAllowed()
+ + ", isHBMCurrentlyAllowed: " + isHbmCurrentlyAllowed()
+ ", isHdrLayerPresent: " + mIsHdrLayerPresent
+ ", mMaxDesiredHdrSdrRatio: " + mMaxDesiredHdrSdrRatio
+ ", isAutoBrightnessEnabled: " + mIsAutoBrightnessEnabled
@@ -575,7 +575,7 @@
return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
} else if (mIsHdrLayerPresent) {
return BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR;
- } else if (isCurrentlyAllowed()) {
+ } else if (isHbmCurrentlyAllowed()) {
return BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 5eaaf35..22e4bc5 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -204,6 +204,13 @@
new SparseArray<>();
/**
+ * If enabled, will not check for {@link Display#FLAG_ROTATES_WITH_CONTENT} in LogicalDisplay
+ * and simply use the {@link DisplayInfo#rotation} supplied by WindowManager via
+ * {@link #setDisplayInfoOverrideFromWindowManagerLocked}
+ */
+ private boolean mAlwaysRotateDisplayDeviceEnabled;
+
+ /**
* If the aspect ratio of the resolution of the display does not match the physical aspect
* ratio of the display, then without this feature enabled, picture would appear stretched to
* the user. This is because applications assume that they are rendered on square pixels
@@ -220,11 +227,11 @@
private final boolean mIsAnisotropyCorrectionEnabled;
LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
- this(displayId, layerStack, primaryDisplayDevice, false);
+ this(displayId, layerStack, primaryDisplayDevice, false, false);
}
LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice,
- boolean isAnisotropyCorrectionEnabled) {
+ boolean isAnisotropyCorrectionEnabled, boolean isAlwaysRotateDisplayDeviceEnabled) {
mDisplayId = displayId;
mLayerStack = layerStack;
mPrimaryDisplayDevice = primaryDisplayDevice;
@@ -236,6 +243,7 @@
mPowerThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
mBaseDisplayInfo.thermalBrightnessThrottlingDataId = mThermalBrightnessThrottlingDataId;
mIsAnisotropyCorrectionEnabled = isAnisotropyCorrectionEnabled;
+ mAlwaysRotateDisplayDeviceEnabled = isAlwaysRotateDisplayDeviceEnabled;
}
public void setDevicePositionLocked(int position) {
@@ -672,7 +680,12 @@
// The orientation specifies how the physical coordinate system of the display
// is rotated when the contents of the logical display are rendered.
int orientation = Surface.ROTATION_0;
- if ((displayDeviceInfo.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0) {
+
+ // FLAG_ROTATES_WITH_CONTENT is now handled in DisplayContent. When the flag
+ // mAlwaysRotateDisplayDeviceEnabled is removed, we should also remove this check for
+ // ROTATES_WITH_CONTENT here and always set the orientation.
+ if ((displayDeviceInfo.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0
+ || mAlwaysRotateDisplayDeviceEnabled) {
orientation = displayInfo.rotation;
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index e092fda..f727eac 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -441,7 +441,7 @@
mVirtualDeviceDisplayMapping.put(displayDevice.getUniqueId(), virtualDeviceUniqueId);
}
- void setDeviceStateLocked(int state, boolean isOverrideActive) {
+ void setDeviceStateLocked(int state) {
if (!mBootCompleted) {
// The boot animation might still be in progress, we do not want to switch states now
// as the boot animation would end up with an incorrect size.
@@ -465,7 +465,7 @@
final boolean wakeDevice = shouldDeviceBeWoken(mPendingDeviceState, mDeviceState,
mInteractive, mBootCompleted);
final boolean sleepDevice = shouldDeviceBePutToSleep(mPendingDeviceState, mDeviceState,
- isOverrideActive, mInteractive, mBootCompleted);
+ mInteractive, mBootCompleted);
// If all displays are off already, we can just transition here, unless we are trying to
// wake or sleep the device as part of this transition. In that case defer the final
@@ -513,8 +513,7 @@
mBootCompleted = true;
if (mDeviceStateToBeAppliedAfterBoot
!= DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER) {
- setDeviceStateLocked(mDeviceStateToBeAppliedAfterBoot,
- /* isOverrideActive= */ false);
+ setDeviceStateLocked(mDeviceStateToBeAppliedAfterBoot);
}
}
}
@@ -560,7 +559,6 @@
*
* @param pendingState device state we are moving to
* @param currentState device state we are currently in
- * @param isOverrideActive if a device state override is currently active or not
* @param isInteractive if the device is in an interactive state
* @param isBootCompleted is the device fully booted
*
@@ -568,13 +566,13 @@
* @see #setDeviceStateLocked
*/
@VisibleForTesting
- boolean shouldDeviceBePutToSleep(int pendingState, int currentState, boolean isOverrideActive,
- boolean isInteractive, boolean isBootCompleted) {
+ boolean shouldDeviceBePutToSleep(int pendingState, int currentState, boolean isInteractive,
+ boolean isBootCompleted) {
return currentState != DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER
&& mDeviceStatesOnWhichToSleep.get(pendingState)
&& !mDeviceStatesOnWhichToSleep.get(currentState)
- && !isOverrideActive
- && isInteractive && isBootCompleted
+ && isInteractive
+ && isBootCompleted
&& !mFoldSettingProvider.shouldStayAwakeOnFold();
}
@@ -1152,7 +1150,8 @@
private LogicalDisplay createNewLogicalDisplayLocked(DisplayDevice device, int displayId) {
final int layerStack = assignLayerStackLocked(displayId);
final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device,
- mFlags.isPixelAnisotropyCorrectionInLogicalDisplayEnabled());
+ mFlags.isPixelAnisotropyCorrectionInLogicalDisplayEnabled(),
+ mFlags.isAlwaysRotateDisplayDeviceEnabled());
display.updateLocked(mDisplayDeviceRepo);
final DisplayInfo info = display.getDisplayInfoLocked();
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index ec5ad7d..bcdb442 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -366,7 +366,8 @@
if (mSurface == null) {
return null;
}
- return mSurface.getDefaultSize();
+ final Point surfaceSize = mSurface.getDefaultSize();
+ return isRotatedLocked() ? new Point(surfaceSize.y, surfaceSize.x) : surfaceSize;
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 15ee937..e1a166e 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -92,9 +92,9 @@
Flags.FLAG_BRIGHTNESS_INT_RANGE_USER_PERCEPTION,
Flags::brightnessIntRangeUserPerception);
- private final FlagState mVsyncProximityVote = new FlagState(
- Flags.FLAG_ENABLE_EXTERNAL_VSYNC_PROXIMITY_VOTE,
- Flags::enableExternalVsyncProximityVote);
+ private final FlagState mRestrictDisplayModes = new FlagState(
+ Flags.FLAG_ENABLE_RESTRICT_DISPLAY_MODES,
+ Flags::enableRestrictDisplayModes);
private final FlagState mVsyncLowPowerVote = new FlagState(
Flags.FLAG_ENABLE_VSYNC_LOW_POWER_VOTE,
@@ -116,6 +116,10 @@
Flags.FLAG_FAST_HDR_TRANSITIONS,
Flags::fastHdrTransitions);
+ private final FlagState mAlwaysRotateDisplayDevice = new FlagState(
+ Flags.FLAG_ALWAYS_ROTATE_DISPLAY_DEVICE,
+ Flags::alwaysRotateDisplayDevice);
+
private final FlagState mRefreshRateVotingTelemetry = new FlagState(
Flags.FLAG_REFRESH_RATE_VOTING_TELEMETRY,
Flags::refreshRateVotingTelemetry
@@ -131,6 +135,11 @@
Flags::sensorBasedBrightnessThrottling
);
+ private final FlagState mIdleScreenRefreshRateTimeout = new FlagState(
+ Flags.FLAG_IDLE_SCREEN_REFRESH_RATE_TIMEOUT,
+ Flags::idleScreenRefreshRateTimeout
+ );
+
private final FlagState mRefactorDisplayPowerController = new FlagState(
Flags.FLAG_REFACTOR_DISPLAY_POWER_CONTROLLER,
@@ -233,8 +242,8 @@
return mBrightnessIntRangeUserPerceptionFlagState.isEnabled();
}
- public boolean isVsyncProximityVoteEnabled() {
- return mVsyncProximityVote.isEnabled();
+ public boolean isRestrictDisplayModesEnabled() {
+ return mRestrictDisplayModes.isEnabled();
}
public boolean isVsyncLowPowerVoteEnabled() {
@@ -260,6 +269,10 @@
return mFastHdrTransitions.isEnabled();
}
+ public boolean isAlwaysRotateDisplayDeviceEnabled() {
+ return mAlwaysRotateDisplayDevice.isEnabled();
+ }
+
public boolean isRefreshRateVotingTelemetryEnabled() {
return mRefreshRateVotingTelemetry.isEnabled();
}
@@ -272,6 +285,10 @@
return mSensorBasedBrightnessThrottling.isEnabled();
}
+ public boolean isIdleScreenRefreshRateTimeoutEnabled() {
+ return mIdleScreenRefreshRateTimeout.isEnabled();
+ }
+
public boolean isRefactorDisplayPowerControllerEnabled() {
return mRefactorDisplayPowerController.isEnabled();
}
@@ -294,13 +311,15 @@
pw.println(" " + mPowerThrottlingClamperFlagState);
pw.println(" " + mSmallAreaDetectionFlagState);
pw.println(" " + mBrightnessIntRangeUserPerceptionFlagState);
- pw.println(" " + mVsyncProximityVote);
+ pw.println(" " + mRestrictDisplayModes);
pw.println(" " + mBrightnessWearBedtimeModeClamperFlagState);
pw.println(" " + mAutoBrightnessModesFlagState);
pw.println(" " + mFastHdrTransitions);
+ pw.println(" " + mAlwaysRotateDisplayDevice);
pw.println(" " + mRefreshRateVotingTelemetry);
pw.println(" " + mPixelAnisotropyCorrectionEnabled);
pw.println(" " + mSensorBasedBrightnessThrottling);
+ pw.println(" " + mIdleScreenRefreshRateTimeout);
pw.println(" " + mRefactorDisplayPowerController);
}
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 9bf36e4..a5f241f 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
@@ -130,9 +130,9 @@
}
flag {
- name: "enable_external_vsync_proximity_vote"
+ name: "enable_restrict_display_modes"
namespace: "display_manager"
- description: "Feature flag for external vsync proximity vote"
+ description: "Feature flag for restriction display modes api"
bug: "284866750"
is_fixed_read_only: true
}
@@ -178,6 +178,17 @@
}
flag {
+ name: "always_rotate_display_device"
+ namespace: "display_manager"
+ description: "Use rotation from WindowManager no matter whether FLAG_ROTATES_WITH_CONTENT is set or not"
+ bug: "302326003"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "refresh_rate_voting_telemetry"
namespace: "display_manager"
description: "Feature flag for enabling telemetry for refresh rate voting in DisplayManager"
@@ -208,3 +219,11 @@
bug: "294444204"
is_fixed_read_only: true
}
+
+flag {
+ name: "idle_screen_refresh_rate_timeout"
+ namespace: "display_manager"
+ description: "Feature flag for reducing the refresh rate when the screen is idle after a timeout"
+ bug: "310026579"
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java b/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java
index c538231..6d750c0 100644
--- a/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java
+++ b/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java
@@ -16,6 +16,8 @@
package com.android.server.display.mode;
+import android.annotation.NonNull;
+
import java.util.Objects;
class BaseModeRefreshRateVote implements Vote {
@@ -31,7 +33,7 @@
}
@Override
- public void updateSummary(VoteSummary summary) {
+ public void updateSummary(@NonNull VoteSummary summary) {
if (summary.appRequestBaseModeRefreshRate == 0f
&& mAppRequestBaseModeRefreshRate > 0f) {
summary.appRequestBaseModeRefreshRate = mAppRequestBaseModeRefreshRate;
diff --git a/services/core/java/com/android/server/display/mode/CombinedVote.java b/services/core/java/com/android/server/display/mode/CombinedVote.java
index 4b68791..3cd16bf 100644
--- a/services/core/java/com/android/server/display/mode/CombinedVote.java
+++ b/services/core/java/com/android/server/display/mode/CombinedVote.java
@@ -16,6 +16,8 @@
package com.android.server.display.mode;
+import android.annotation.NonNull;
+
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -28,7 +30,7 @@
}
@Override
- public void updateSummary(VoteSummary summary) {
+ public void updateSummary(@NonNull VoteSummary summary) {
mVotes.forEach(vote -> vote.updateSummary(summary));
}
diff --git a/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java b/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java
index 7f57406..7abb518 100644
--- a/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java
+++ b/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java
@@ -16,6 +16,8 @@
package com.android.server.display.mode;
+import android.annotation.NonNull;
+
import java.util.Objects;
class DisableRefreshRateSwitchingVote implements Vote {
@@ -31,7 +33,7 @@
}
@Override
- public void updateSummary(VoteSummary summary) {
+ public void updateSummary(@NonNull VoteSummary summary) {
summary.disableRefreshRateSwitching =
summary.disableRefreshRateSwitching || mDisableRefreshRateSwitching;
}
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 64cbd54..495ae87 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -41,6 +41,7 @@
import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.net.Uri;
import android.os.Handler;
+import android.os.IBinder;
import android.os.IThermalEventListener;
import android.os.IThermalService;
import android.os.Looper;
@@ -80,7 +81,6 @@
import com.android.server.display.utils.DeviceConfigParsingUtils;
import com.android.server.display.utils.SensorUtils;
import com.android.server.sensors.SensorManagerInternal;
-import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener;
import com.android.server.statusbar.StatusBarManagerInternal;
import java.io.PrintWriter;
@@ -128,9 +128,12 @@
private final SettingsObserver mSettingsObserver;
private final DisplayObserver mDisplayObserver;
private final UdfpsObserver mUdfpsObserver;
- private final SensorObserver mSensorObserver;
+ private final ProximitySensorObserver mSensorObserver;
private final HbmObserver mHbmObserver;
private final SkinThermalStatusObserver mSkinThermalStatusObserver;
+
+ @Nullable
+ private final SystemRequestObserver mSystemRequestObserver;
private final DeviceConfigParameterProvider mConfigParameterProvider;
private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
@@ -203,6 +206,7 @@
.isDisplaysRefreshRatesSynchronizationEnabled();
mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled = displayManagerFlags
.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled();
+
mContext = context;
mHandler = new DisplayModeDirectorHandler(handler.getLooper());
mInjector = injector;
@@ -222,10 +226,15 @@
mVotesStorage = new VotesStorage(this::notifyDesiredDisplayModeSpecsChangedLocked,
mVotesStatsReporter);
mDisplayObserver = new DisplayObserver(context, handler, mVotesStorage);
- mSensorObserver = new SensorObserver(context, mVotesStorage, injector);
+ mSensorObserver = new ProximitySensorObserver(mVotesStorage, injector);
mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, mVotesStorage);
mHbmObserver = new HbmObserver(injector, mVotesStorage, BackgroundThread.getHandler(),
mDeviceConfigDisplaySettings);
+ if (mDvrrSupported && displayManagerFlags.isRestrictDisplayModesEnabled()) {
+ mSystemRequestObserver = new SystemRequestObserver(mVotesStorage);
+ } else {
+ mSystemRequestObserver = null;
+ }
mAlwaysRespectAppRequest = false;
mSupportsFrameRateOverride = injector.supportsFrameRateOverride();
}
@@ -520,6 +529,15 @@
}
/**
+ * Delegates requestDisplayModes call to SystemRequestObserver
+ */
+ public void requestDisplayModes(IBinder token, int displayId, int[] modeIds) {
+ if (mSystemRequestObserver != null) {
+ mSystemRequestObserver.requestDisplayModes(token, displayId, modeIds);
+ }
+ }
+
+ /**
* Print the object's state and debug information into the given stream.
*
* @param pw The stream to dump information to.
@@ -970,10 +988,10 @@
Settings.Global.LOW_POWER_MODE, 0 /*default*/) != 0;
final Vote vote;
if (inLowPowerMode && mVsynLowPowerVoteEnabled) {
- vote = Vote.forSupportedModes(List.of(
- new SupportedModesVote.SupportedMode(/* peakRefreshRate= */ 60f,
+ vote = Vote.forSupportedRefreshRates(List.of(
+ new SupportedRefreshRatesVote.RefreshRates(/* peakRefreshRate= */ 60f,
/* vsyncRate= */ 240f),
- new SupportedModesVote.SupportedMode(/* peakRefreshRate= */ 60f,
+ new SupportedRefreshRatesVote.RefreshRates(/* peakRefreshRate= */ 60f,
/* vsyncRate= */ 60f)
));
} else if (inLowPowerMode) {
@@ -2158,11 +2176,11 @@
}
if (mVsyncLowLightBlockingVoteEnabled) {
- refreshRateSwitchingVote = Vote.forSupportedModesAndDisableRefreshRateSwitching(
+ refreshRateSwitchingVote = Vote.forSupportedRefreshRatesAndDisableSwitching(
List.of(
- new SupportedModesVote.SupportedMode(
+ new SupportedRefreshRatesVote.RefreshRates(
/* peakRefreshRate= */ 60f, /* vsyncRate= */ 60f),
- new SupportedModesVote.SupportedMode(
+ new SupportedRefreshRatesVote.RefreshRates(
/* peakRefreshRate= */120f, /* vsyncRate= */ 120f)));
} else {
refreshRateSwitchingVote = Vote.forDisableRefreshRateSwitching();
@@ -2498,116 +2516,6 @@
}
}
- protected static final class SensorObserver implements ProximityActiveListener,
- DisplayManager.DisplayListener {
- private final String mProximitySensorName = null;
- private final String mProximitySensorType = Sensor.STRING_TYPE_PROXIMITY;
-
- private final VotesStorage mVotesStorage;
- private final Context mContext;
- private final Injector mInjector;
- @GuardedBy("mSensorObserverLock")
- private final SparseBooleanArray mDozeStateByDisplay = new SparseBooleanArray();
- private final Object mSensorObserverLock = new Object();
-
- private DisplayManager mDisplayManager;
- private DisplayManagerInternal mDisplayManagerInternal;
- @GuardedBy("mSensorObserverLock")
- private boolean mIsProxActive = false;
-
- SensorObserver(Context context, VotesStorage votesStorage, Injector injector) {
- mContext = context;
- mVotesStorage = votesStorage;
- mInjector = injector;
- }
-
- @Override
- public void onProximityActive(boolean isActive) {
- synchronized (mSensorObserverLock) {
- if (mIsProxActive != isActive) {
- mIsProxActive = isActive;
- recalculateVotesLocked();
- }
- }
- }
-
- public void observe() {
- mDisplayManager = mContext.getSystemService(DisplayManager.class);
- mDisplayManagerInternal = mInjector.getDisplayManagerInternal();
-
- final SensorManagerInternal sensorManager = mInjector.getSensorManagerInternal();
- sensorManager.addProximityActiveListener(BackgroundThread.getExecutor(), this);
-
- synchronized (mSensorObserverLock) {
- for (Display d : mInjector.getDisplays()) {
- mDozeStateByDisplay.put(d.getDisplayId(), mInjector.isDozeState(d));
- }
- }
- mInjector.registerDisplayListener(this, BackgroundThread.getHandler(),
- DisplayManager.EVENT_FLAG_DISPLAY_ADDED
- | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
- | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
- }
-
- private void recalculateVotesLocked() {
- final Display[] displays = mInjector.getDisplays();
- for (Display d : displays) {
- int displayId = d.getDisplayId();
- Vote vote = null;
- if (mIsProxActive && !mDozeStateByDisplay.get(displayId)) {
- final RefreshRateRange rate =
- mDisplayManagerInternal.getRefreshRateForDisplayAndSensor(
- displayId, mProximitySensorName, mProximitySensorType);
- if (rate != null) {
- vote = Vote.forPhysicalRefreshRates(rate.min, rate.max);
- }
- }
- mVotesStorage.updateVote(displayId, Vote.PRIORITY_PROXIMITY, vote);
- }
- }
-
- void dump(PrintWriter pw) {
- pw.println(" SensorObserver");
- synchronized (mSensorObserverLock) {
- pw.println(" mIsProxActive=" + mIsProxActive);
- pw.println(" mDozeStateByDisplay:");
- for (int i = 0; i < mDozeStateByDisplay.size(); i++) {
- final int id = mDozeStateByDisplay.keyAt(i);
- final boolean dozed = mDozeStateByDisplay.valueAt(i);
- pw.println(" " + id + " -> " + dozed);
- }
- }
- }
-
- @Override
- public void onDisplayAdded(int displayId) {
- boolean isDozeState = mInjector.isDozeState(mInjector.getDisplay(displayId));
- synchronized (mSensorObserverLock) {
- mDozeStateByDisplay.put(displayId, isDozeState);
- recalculateVotesLocked();
- }
- }
-
- @Override
- public void onDisplayChanged(int displayId) {
- boolean wasDozeState = mDozeStateByDisplay.get(displayId);
- synchronized (mSensorObserverLock) {
- mDozeStateByDisplay.put(displayId,
- mInjector.isDozeState(mInjector.getDisplay(displayId)));
- if (wasDozeState != mDozeStateByDisplay.get(displayId)) {
- recalculateVotesLocked();
- }
- }
- }
-
- @Override
- public void onDisplayRemoved(int displayId) {
- synchronized (mSensorObserverLock) {
- mDozeStateByDisplay.delete(displayId);
- recalculateVotesLocked();
- }
- }
- }
/**
* Listens to DisplayManager for HBM status and applies any refresh-rate restrictions for
diff --git a/services/core/java/com/android/server/display/mode/ProximitySensorObserver.java b/services/core/java/com/android/server/display/mode/ProximitySensorObserver.java
new file mode 100644
index 0000000..11418c1
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/ProximitySensorObserver.java
@@ -0,0 +1,138 @@
+/*
+ * 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.display.mode;
+
+import android.hardware.Sensor;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.util.SparseBooleanArray;
+import android.view.Display;
+import android.view.SurfaceControl;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
+import com.android.server.sensors.SensorManagerInternal;
+
+import java.io.PrintWriter;
+
+class ProximitySensorObserver implements
+ SensorManagerInternal.ProximityActiveListener,
+ DisplayManager.DisplayListener {
+ private final String mProximitySensorName = null;
+ private final String mProximitySensorType = Sensor.STRING_TYPE_PROXIMITY;
+
+ private final VotesStorage mVotesStorage;
+ private final DisplayModeDirector.Injector mInjector;
+ @GuardedBy("mSensorObserverLock")
+ private final SparseBooleanArray mDozeStateByDisplay = new SparseBooleanArray();
+ private final Object mSensorObserverLock = new Object();
+ private DisplayManagerInternal mDisplayManagerInternal;
+ @GuardedBy("mSensorObserverLock")
+ private boolean mIsProxActive = false;
+
+ ProximitySensorObserver(VotesStorage votesStorage, DisplayModeDirector.Injector injector) {
+ mVotesStorage = votesStorage;
+ mInjector = injector;
+ }
+
+ @Override
+ public void onProximityActive(boolean isActive) {
+ synchronized (mSensorObserverLock) {
+ if (mIsProxActive != isActive) {
+ mIsProxActive = isActive;
+ recalculateVotesLocked();
+ }
+ }
+ }
+
+ void observe() {
+ mDisplayManagerInternal = mInjector.getDisplayManagerInternal();
+
+ final SensorManagerInternal sensorManager = mInjector.getSensorManagerInternal();
+ sensorManager.addProximityActiveListener(BackgroundThread.getExecutor(), this);
+
+ synchronized (mSensorObserverLock) {
+ for (Display d : mInjector.getDisplays()) {
+ mDozeStateByDisplay.put(d.getDisplayId(), mInjector.isDozeState(d));
+ }
+ }
+ mInjector.registerDisplayListener(this, BackgroundThread.getHandler(),
+ DisplayManager.EVENT_FLAG_DISPLAY_ADDED
+ | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
+ }
+
+ @GuardedBy("mSensorObserverLock")
+ private void recalculateVotesLocked() {
+ final Display[] displays = mInjector.getDisplays();
+ for (Display d : displays) {
+ int displayId = d.getDisplayId();
+ Vote vote = null;
+ if (mIsProxActive && !mDozeStateByDisplay.get(displayId)) {
+ final SurfaceControl.RefreshRateRange rate =
+ mDisplayManagerInternal.getRefreshRateForDisplayAndSensor(
+ displayId, mProximitySensorName, mProximitySensorType);
+ if (rate != null) {
+ vote = Vote.forPhysicalRefreshRates(rate.min, rate.max);
+ }
+ }
+ mVotesStorage.updateVote(displayId, Vote.PRIORITY_PROXIMITY, vote);
+ }
+ }
+
+ void dump(PrintWriter pw) {
+ pw.println(" SensorObserver");
+ synchronized (mSensorObserverLock) {
+ pw.println(" mIsProxActive=" + mIsProxActive);
+ pw.println(" mDozeStateByDisplay:");
+ for (int i = 0; i < mDozeStateByDisplay.size(); i++) {
+ final int id = mDozeStateByDisplay.keyAt(i);
+ final boolean dozed = mDozeStateByDisplay.valueAt(i);
+ pw.println(" " + id + " -> " + dozed);
+ }
+ }
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ boolean isDozeState = mInjector.isDozeState(mInjector.getDisplay(displayId));
+ synchronized (mSensorObserverLock) {
+ mDozeStateByDisplay.put(displayId, isDozeState);
+ recalculateVotesLocked();
+ }
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ synchronized (mSensorObserverLock) {
+ boolean wasDozeState = mDozeStateByDisplay.get(displayId);
+ mDozeStateByDisplay.put(displayId,
+ mInjector.isDozeState(mInjector.getDisplay(displayId)));
+ if (wasDozeState != mDozeStateByDisplay.get(displayId)) {
+ recalculateVotesLocked();
+ }
+ }
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ synchronized (mSensorObserverLock) {
+ mDozeStateByDisplay.delete(displayId);
+ recalculateVotesLocked();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/mode/RefreshRateVote.java b/services/core/java/com/android/server/display/mode/RefreshRateVote.java
index 670b8a1..b96ab3b 100644
--- a/services/core/java/com/android/server/display/mode/RefreshRateVote.java
+++ b/services/core/java/com/android/server/display/mode/RefreshRateVote.java
@@ -16,6 +16,8 @@
package com.android.server.display.mode;
+import android.annotation.NonNull;
+
import java.util.Objects;
@@ -64,7 +66,7 @@
* Vote: min(ignored) min(applied) min(applied+physical) max(applied) max(ignored)
*/
@Override
- public void updateSummary(VoteSummary summary) {
+ public void updateSummary(@NonNull VoteSummary summary) {
summary.minRenderFrameRate = Math.max(summary.minRenderFrameRate, mMinRefreshRate);
summary.maxRenderFrameRate = Math.min(summary.maxRenderFrameRate, mMaxRefreshRate);
// Physical refresh rate cannot be lower than the minimal render frame rate.
@@ -97,7 +99,7 @@
* Vote: min(ignored) min(applied) max(applied+render) max(applied) max(ignored)
*/
@Override
- public void updateSummary(VoteSummary summary) {
+ public void updateSummary(@NonNull VoteSummary summary) {
summary.minPhysicalRefreshRate = Math.max(summary.minPhysicalRefreshRate,
mMinRefreshRate);
summary.maxPhysicalRefreshRate = Math.min(summary.maxPhysicalRefreshRate,
diff --git a/services/core/java/com/android/server/display/mode/SizeVote.java b/services/core/java/com/android/server/display/mode/SizeVote.java
index f2f8dc4..f5a5abe 100644
--- a/services/core/java/com/android/server/display/mode/SizeVote.java
+++ b/services/core/java/com/android/server/display/mode/SizeVote.java
@@ -16,6 +16,8 @@
package com.android.server.display.mode;
+import android.annotation.NonNull;
+
import java.util.Objects;
class SizeVote implements Vote {
@@ -48,7 +50,7 @@
}
@Override
- public void updateSummary(VoteSummary summary) {
+ public void updateSummary(@NonNull VoteSummary summary) {
if (mHeight > 0 && mWidth > 0) {
// For display size, disable refresh rate switching and base mode refresh rate use
// only the first vote we come across (i.e. the highest priority vote that includes
diff --git a/services/core/java/com/android/server/display/mode/SupportedModesVote.java b/services/core/java/com/android/server/display/mode/SupportedModesVote.java
index 7eebcc0..0cf8311 100644
--- a/services/core/java/com/android/server/display/mode/SupportedModesVote.java
+++ b/services/core/java/com/android/server/display/mode/SupportedModesVote.java
@@ -16,77 +16,42 @@
package com.android.server.display.mode;
-import java.util.ArrayList;
+import android.annotation.NonNull;
+
import java.util.Collections;
import java.util.List;
import java.util.Objects;
-class SupportedModesVote implements Vote {
+public class SupportedModesVote implements Vote {
- final List<SupportedMode> mSupportedModes;
+ final List<Integer> mModeIds;
- SupportedModesVote(List<SupportedMode> supportedModes) {
- mSupportedModes = Collections.unmodifiableList(supportedModes);
+ SupportedModesVote(List<Integer> modeIds) {
+ mModeIds = Collections.unmodifiableList(modeIds);
+ }
+ @Override
+ public void updateSummary(@NonNull VoteSummary summary) {
+ if (summary.supportedModeIds == null) {
+ summary.supportedModeIds = mModeIds;
+ } else {
+ summary.supportedModeIds.retainAll(mModeIds);
+ }
}
- /**
- * Summary should have subset of supported modes.
- * If Vote1.supportedModes=(A,B), Vote2.supportedModes=(B,C) then summary.supportedModes=(B)
- * If summary.supportedModes==null then there is no restriction on supportedModes
- */
@Override
- public void updateSummary(VoteSummary summary) {
- if (summary.supportedModes == null) {
- summary.supportedModes = new ArrayList<>(mSupportedModes);
- } else {
- summary.supportedModes.retainAll(mSupportedModes);
- }
+ public String toString() {
+ return "SupportedModesVote{ mModeIds=" + mModeIds + " }";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof SupportedModesVote that)) return false;
- return mSupportedModes.equals(that.mSupportedModes);
+ return mModeIds.equals(that.mModeIds);
}
@Override
public int hashCode() {
- return Objects.hash(mSupportedModes);
- }
-
- @Override
- public String toString() {
- return "SupportedModesVote{ mSupportedModes=" + mSupportedModes + " }";
- }
-
- static class SupportedMode {
- final float mPeakRefreshRate;
- final float mVsyncRate;
-
-
- SupportedMode(float peakRefreshRate, float vsyncRate) {
- mPeakRefreshRate = peakRefreshRate;
- mVsyncRate = vsyncRate;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof SupportedMode that)) return false;
- return Float.compare(that.mPeakRefreshRate, mPeakRefreshRate) == 0
- && Float.compare(that.mVsyncRate, mVsyncRate) == 0;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mPeakRefreshRate, mVsyncRate);
- }
-
- @Override
- public String toString() {
- return "SupportedMode{ mPeakRefreshRate=" + mPeakRefreshRate
- + ", mVsyncRate=" + mVsyncRate + " }";
- }
+ return Objects.hash(mModeIds);
}
}
diff --git a/services/core/java/com/android/server/display/mode/SupportedRefreshRatesVote.java b/services/core/java/com/android/server/display/mode/SupportedRefreshRatesVote.java
new file mode 100644
index 0000000..5305487
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/SupportedRefreshRatesVote.java
@@ -0,0 +1,94 @@
+/*
+ * 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.display.mode;
+
+import android.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+class SupportedRefreshRatesVote implements Vote {
+
+ final List<RefreshRates> mRefreshRates;
+
+ SupportedRefreshRatesVote(List<RefreshRates> refreshRates) {
+ mRefreshRates = Collections.unmodifiableList(refreshRates);
+ }
+
+ /**
+ * Summary should have subset of supported modes.
+ * If Vote1.refreshRates=(A,B), Vote2.refreshRates=(B,C)
+ * then summary.supportedRefreshRates=(B)
+ * If summary.supportedRefreshRates==null then there is no restriction on supportedRefreshRates
+ */
+ @Override
+ public void updateSummary(@NonNull VoteSummary summary) {
+ if (summary.supportedRefreshRates == null) {
+ summary.supportedRefreshRates = new ArrayList<>(mRefreshRates);
+ } else {
+ summary.supportedRefreshRates.retainAll(mRefreshRates);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SupportedRefreshRatesVote that)) return false;
+ return mRefreshRates.equals(that.mRefreshRates);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mRefreshRates);
+ }
+
+ @Override
+ public String toString() {
+ return "SupportedRefreshRatesVote{ mSupportedModes=" + mRefreshRates + " }";
+ }
+
+ static class RefreshRates {
+ final float mPeakRefreshRate;
+ final float mVsyncRate;
+
+ RefreshRates(float peakRefreshRate, float vsyncRate) {
+ mPeakRefreshRate = peakRefreshRate;
+ mVsyncRate = vsyncRate;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof RefreshRates that)) return false;
+ return Float.compare(that.mPeakRefreshRate, mPeakRefreshRate) == 0
+ && Float.compare(that.mVsyncRate, mVsyncRate) == 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPeakRefreshRate, mVsyncRate);
+ }
+
+ @Override
+ public String toString() {
+ return "RefreshRates{ mPeakRefreshRate=" + mPeakRefreshRate
+ + ", mVsyncRate=" + mVsyncRate + " }";
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/mode/SystemRequestObserver.java b/services/core/java/com/android/server/display/mode/SystemRequestObserver.java
new file mode 100644
index 0000000..15f19cc
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/SystemRequestObserver.java
@@ -0,0 +1,139 @@
+/*
+ * 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.display.mode;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * SystemRequestObserver responsible for handling system requests to filter allowable display
+ * modes
+ */
+class SystemRequestObserver {
+ private final VotesStorage mVotesStorage;
+
+ private final IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ // noop, binderDied(@NonNull IBinder who) is overridden
+ }
+ @Override
+ public void binderDied(@NonNull IBinder who) {
+ removeSystemRequestedVotes(who);
+ who.unlinkToDeath(mDeathRecipient, 0);
+ }
+ };
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final Map<IBinder, SparseArray<List<Integer>>> mDisplaysRestrictions = new HashMap<>();
+
+ SystemRequestObserver(VotesStorage storage) {
+ mVotesStorage = storage;
+ }
+
+ void requestDisplayModes(IBinder token, int displayId, @Nullable int[] modeIds) {
+ if (modeIds == null) {
+ removeSystemRequestedVote(token, displayId);
+ } else {
+ addSystemRequestedVote(token, displayId, modeIds);
+ }
+ }
+
+ private void addSystemRequestedVote(IBinder token, int displayId, @NonNull int[] modeIds) {
+ try {
+ boolean needLinkToDeath = false;
+ List<Integer> modeIdsList = new ArrayList<>();
+ for (int mode: modeIds) {
+ modeIdsList.add(mode);
+ }
+ synchronized (mLock) {
+ SparseArray<List<Integer>> modesByDisplay = mDisplaysRestrictions.get(token);
+ if (modesByDisplay == null) {
+ needLinkToDeath = true;
+ modesByDisplay = new SparseArray<>();
+ mDisplaysRestrictions.put(token, modesByDisplay);
+ }
+
+ modesByDisplay.put(displayId, modeIdsList);
+ updateStorageLocked(displayId);
+ }
+ if (needLinkToDeath) {
+ token.linkToDeath(mDeathRecipient, 0);
+ }
+ } catch (RemoteException re) {
+ removeSystemRequestedVotes(token);
+ }
+ }
+
+ private void removeSystemRequestedVote(IBinder token, int displayId) {
+ boolean needToUnlink = false;
+ synchronized (mLock) {
+ SparseArray<List<Integer>> modesByDisplay = mDisplaysRestrictions.get(token);
+ if (modesByDisplay != null) {
+ modesByDisplay.remove(displayId);
+ needToUnlink = modesByDisplay.size() == 0;
+ updateStorageLocked(displayId);
+ }
+ }
+ if (needToUnlink) {
+ token.unlinkToDeath(mDeathRecipient, 0);
+ }
+ }
+
+ private void removeSystemRequestedVotes(IBinder token) {
+ synchronized (mLock) {
+ SparseArray<List<Integer>> removed = mDisplaysRestrictions.remove(token);
+ if (removed != null) {
+ for (int i = 0; i < removed.size(); i++) {
+ updateStorageLocked(removed.keyAt(i));
+ }
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void updateStorageLocked(int displayId) {
+ List<Integer> modeIds = new ArrayList<>();
+ boolean[] modesFound = new boolean[1];
+
+ mDisplaysRestrictions.forEach((key, value) -> {
+ List<Integer> modesForDisplay = value.get(displayId);
+ if (modesForDisplay != null) {
+ if (!modesFound[0]) {
+ modeIds.addAll(modesForDisplay);
+ modesFound[0] = true;
+ } else {
+ modeIds.retainAll(modesForDisplay);
+ }
+ }
+ });
+
+ mVotesStorage.updateVote(displayId, Vote.PRIORITY_SYSTEM_REQUESTED_MODES,
+ modesFound[0] ? Vote.forSupportedModes(modeIds) : null);
+ }
+}
diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java
index e8d5a19..5b987f4 100644
--- a/services/core/java/com/android/server/display/mode/Vote.java
+++ b/services/core/java/com/android/server/display/mode/Vote.java
@@ -16,6 +16,8 @@
package com.android.server.display.mode;
+import android.annotation.NonNull;
+
import java.util.List;
interface Vote {
@@ -91,26 +93,29 @@
// For concurrent displays we want to limit refresh rate on all displays
int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 12;
+ // For internal application to limit display modes to specific ids
+ int PRIORITY_SYSTEM_REQUESTED_MODES = 13;
+
// LOW_POWER_MODE force the render frame rate to [0, 60HZ] if
// Settings.Global.LOW_POWER_MODE is on.
- int PRIORITY_LOW_POWER_MODE = 13;
+ int PRIORITY_LOW_POWER_MODE = 14;
// PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the
// higher priority voters' result is a range, it will fix the rate to a single choice.
// It's used to avoid refresh rate switches in certain conditions which may result in the
// user seeing the display flickering when the switches occur.
- int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 14;
+ int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 15;
// Force display to [0, 60HZ] if skin temperature is at or above CRITICAL.
- int PRIORITY_SKIN_TEMPERATURE = 15;
+ int PRIORITY_SKIN_TEMPERATURE = 16;
// The proximity sensor needs the refresh rate to be locked in order to function, so this is
// set to a high priority.
- int PRIORITY_PROXIMITY = 16;
+ int PRIORITY_PROXIMITY = 17;
// The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order
// to function, so this needs to be the highest priority of all votes.
- int PRIORITY_UDFPS = 17;
+ int PRIORITY_UDFPS = 18;
// Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and
// APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString.
@@ -128,7 +133,7 @@
*/
int INVALID_SIZE = -1;
- void updateSummary(VoteSummary summary);
+ void updateSummary(@NonNull VoteSummary summary);
static Vote forPhysicalRefreshRates(float minRefreshRate, float maxRefreshRate) {
return new CombinedVote(
@@ -166,15 +171,22 @@
return new BaseModeRefreshRateVote(baseModeRefreshRate);
}
- static Vote forSupportedModes(List<SupportedModesVote.SupportedMode> supportedModes) {
- return new SupportedModesVote(supportedModes);
+ static Vote forSupportedRefreshRates(
+ List<SupportedRefreshRatesVote.RefreshRates> refreshRates) {
+ return new SupportedRefreshRatesVote(refreshRates);
+ }
+
+ static Vote forSupportedModes(List<Integer> modeIds) {
+ return new SupportedModesVote(modeIds);
}
- static Vote forSupportedModesAndDisableRefreshRateSwitching(
- List<SupportedModesVote.SupportedMode> supportedModes) {
+
+ static Vote forSupportedRefreshRatesAndDisableSwitching(
+ List<SupportedRefreshRatesVote.RefreshRates> supportedRefreshRates) {
return new CombinedVote(
- List.of(forDisableRefreshRateSwitching(), forSupportedModes(supportedModes)));
+ List.of(forDisableRefreshRateSwitching(),
+ forSupportedRefreshRates(supportedRefreshRates)));
}
static String priorityToString(int priority) {
diff --git a/services/core/java/com/android/server/display/mode/VoteSummary.java b/services/core/java/com/android/server/display/mode/VoteSummary.java
index 5fc36b5..d4ce892 100644
--- a/services/core/java/com/android/server/display/mode/VoteSummary.java
+++ b/services/core/java/com/android/server/display/mode/VoteSummary.java
@@ -16,6 +16,7 @@
package com.android.server.display.mode;
+import android.annotation.Nullable;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
@@ -39,7 +40,11 @@
public boolean disableRefreshRateSwitching;
public float appRequestBaseModeRefreshRate;
- public List<SupportedModesVote.SupportedMode> supportedModes;
+ @Nullable
+ public List<SupportedRefreshRatesVote.RefreshRates> supportedRefreshRates;
+
+ @Nullable
+ public List<Integer> supportedModeIds;
final boolean mIsDisplayResolutionRangeVotingEnabled;
@@ -112,6 +117,9 @@
boolean missingBaseModeRefreshRate = appRequestBaseModeRefreshRate > 0f;
for (Display.Mode mode : modes) {
+ if (!validateRefreshRatesSupported(mode)) {
+ continue;
+ }
if (!validateModeSupported(mode)) {
continue;
}
@@ -253,21 +261,37 @@
}
private boolean validateModeSupported(Display.Mode mode) {
- if (supportedModes == null || !mSupportedModesVoteEnabled) {
+ if (supportedModeIds == null || !mSupportedModesVoteEnabled) {
return true;
}
- for (SupportedModesVote.SupportedMode supportedMode : supportedModes) {
- if (equalsWithinFloatTolerance(mode.getRefreshRate(), supportedMode.mPeakRefreshRate)
- && equalsWithinFloatTolerance(mode.getVsyncRate(), supportedMode.mVsyncRate)) {
+ if (supportedModeIds.contains(mode.getModeId())) {
+ return true;
+ }
+ if (mLoggingEnabled) {
+ Slog.w(TAG, "Discarding mode " + mode.getModeId()
+ + ", supportedMode not found"
+ + ": mode.modeId=" + mode.getModeId()
+ + ", supportedModeIds=" + supportedModeIds);
+ }
+ return false;
+ }
+
+ private boolean validateRefreshRatesSupported(Display.Mode mode) {
+ if (supportedRefreshRates == null || !mSupportedModesVoteEnabled) {
+ return true;
+ }
+ for (SupportedRefreshRatesVote.RefreshRates refreshRates : this.supportedRefreshRates) {
+ if (equalsWithinFloatTolerance(mode.getRefreshRate(), refreshRates.mPeakRefreshRate)
+ && equalsWithinFloatTolerance(mode.getVsyncRate(), refreshRates.mVsyncRate)) {
return true;
}
}
if (mLoggingEnabled) {
Slog.w(TAG, "Discarding mode " + mode.getModeId()
- + ", supportedMode not found"
+ + ", supportedRefreshRates not found"
+ ": mode.refreshRate=" + mode.getRefreshRate()
+ ", mode.vsyncRate=" + mode.getVsyncRate()
- + ", supportedModes=" + supportedModes);
+ + ", supportedRefreshRates=" + supportedRefreshRates);
}
return false;
}
@@ -298,7 +322,8 @@
return false;
}
- if (supportedModes != null && mSupportedModesVoteEnabled && supportedModes.isEmpty()) {
+ if (supportedRefreshRates != null && mSupportedModesVoteEnabled
+ && supportedRefreshRates.isEmpty()) {
if (mLoggingEnabled) {
Slog.w(TAG, "Vote summary resulted in empty set (empty supportedModes)");
}
@@ -345,7 +370,8 @@
minHeight = 0;
disableRefreshRateSwitching = false;
appRequestBaseModeRefreshRate = 0f;
- supportedModes = null;
+ supportedRefreshRates = null;
+ supportedModeIds = null;
if (mLoggingEnabled) {
Slog.i(TAG, "Summary reset: " + this);
}
@@ -367,7 +393,8 @@
+ ", minHeight=" + minHeight
+ ", disableRefreshRateSwitching=" + disableRefreshRateSwitching
+ ", appRequestBaseModeRefreshRate=" + appRequestBaseModeRefreshRate
- + ", supportedModes=" + supportedModes
+ + ", supportedRefreshRates=" + supportedRefreshRates
+ + ", supportedModeIds=" + supportedModeIds
+ ", mIsDisplayResolutionRangeVotingEnabled="
+ mIsDisplayResolutionRangeVotingEnabled
+ ", mSupportedModesVoteEnabled=" + mSupportedModesVoteEnabled
diff --git a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
index e80b9451..7562a52 100644
--- a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
+++ b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
@@ -117,11 +117,11 @@
maxRefreshRate = (int) physicalVote.mMaxRefreshRate;
} else if (!ignoreRenderRate && (vote instanceof RefreshRateVote.RenderVote renderVote)) {
maxRefreshRate = (int) renderVote.mMaxRefreshRate;
- } else if (vote instanceof SupportedModesVote supportedModesVote) {
- // SupportedModesVote limits mode by specific refreshRates, so highest rr is allowed
+ } else if (vote instanceof SupportedRefreshRatesVote refreshRatesVote) {
+ // SupportedRefreshRatesVote limits mode by refreshRates, so highest rr is allowed
maxRefreshRate = 0;
- for (SupportedModesVote.SupportedMode mode : supportedModesVote.mSupportedModes) {
- maxRefreshRate = Math.max(maxRefreshRate, (int) mode.mPeakRefreshRate);
+ for (SupportedRefreshRatesVote.RefreshRates rr : refreshRatesVote.mRefreshRates) {
+ maxRefreshRate = Math.max(maxRefreshRate, (int) rr.mPeakRefreshRate);
}
} else if (vote instanceof CombinedVote combinedVote) {
for (Vote subVote: combinedVote.mVotes) {
diff --git a/services/core/java/com/android/server/display/mode/VotesStorage.java b/services/core/java/com/android/server/display/mode/VotesStorage.java
index 56c7c18..6becf1c 100644
--- a/services/core/java/com/android/server/display/mode/VotesStorage.java
+++ b/services/core/java/com/android/server/display/mode/VotesStorage.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
@@ -124,6 +125,44 @@
}
}
+ /** removes all votes with certain priority from vote storage */
+ void removeAllVotesForPriority(int priority) {
+ if (mLoggingEnabled) {
+ Slog.i(TAG, "removeAllVotesForPriority(priority="
+ + Vote.priorityToString(priority) + ")");
+ }
+ if (priority < Vote.MIN_PRIORITY || priority > Vote.MAX_PRIORITY) {
+ Slog.w(TAG, "Received an invalid priority, ignoring:"
+ + " priority=" + Vote.priorityToString(priority));
+ return;
+ }
+ IntArray removedVotesDisplayIds = new IntArray();
+ synchronized (mStorageLock) {
+ int size = mVotesByDisplay.size();
+ for (int i = 0; i < size; i++) {
+ SparseArray<Vote> votes = mVotesByDisplay.valueAt(i);
+ if (votes.get(priority) != null) {
+ votes.remove(priority);
+ removedVotesDisplayIds.add(mVotesByDisplay.keyAt(i));
+ }
+ }
+ }
+ if (mLoggingEnabled) {
+ Slog.i(TAG, "Removed votes with priority=" + priority
+ + " for displays=" + removedVotesDisplayIds);
+ }
+ int removedVotesSize = removedVotesDisplayIds.size();
+ if (removedVotesSize > 0) {
+ if (mVotesStatsReporter != null) {
+ for (int i = 0; i < removedVotesSize; i++) {
+ mVotesStatsReporter.reportVoteChanged(
+ removedVotesDisplayIds.get(i), priority, null);
+ }
+ }
+ mListener.onChanged();
+ }
+ }
+
/** dump class values, for debugging */
void dump(@NonNull PrintWriter pw) {
SparseArray<SparseArray<Vote>> votesByDisplayLocal = new SparseArray<>();
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index e6bf2c9..3fafca8 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -266,7 +266,11 @@
throw new RuntimeException(e);
}
}
+ updateConfiguration(grammaticalGender, userId);
+ Trace.endSection();
+ }
+ private void updateConfiguration(int grammaticalGender, int userId) {
try {
Configuration config = new Configuration();
int preValue = config.getGrammaticalGender();
@@ -280,7 +284,6 @@
} catch (RemoteException e) {
Log.w(TAG, "Can not update configuration", e);
}
- Trace.endSection();
}
public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
@@ -372,7 +375,9 @@
if (mGrammaticalGenderCache.indexOfKey(userId) < 0) {
try (FileInputStream in = new FileInputStream(file)) {
final TypedXmlPullParser parser = Xml.resolvePullParser(in);
- mGrammaticalGenderCache.put(userId, getGrammaticalGenderFromXml(parser));
+ int grammaticalGender = getGrammaticalGenderFromXml(parser);
+ mGrammaticalGenderCache.put(userId, grammaticalGender);
+ updateConfiguration(grammaticalGender, userId);
} catch (IOException | XmlPullParserException e) {
Log.e(TAG, "Failed to parse XML configuration from " + file, e);
}
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index b8ae737..f21fd41 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -146,6 +146,10 @@
SPLIT_SCREEN_NAVIGATION(
FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SPLIT_SCREEN_NAVIGATION,
"SPLIT_SCREEN_NAVIGATION"),
+
+ CHANGE_SPLITSCREEN_FOCUS(
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__CHANGE_SPLITSCREEN_FOCUS,
+ "CHANGE_SPLITSCREEN_FOCUS"),
TRIGGER_BUG_REPORT(
FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT,
"TRIGGER_BUG_REPORT"),
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index c7b60da..dd6433d 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -19,11 +19,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Handler;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.DirectBootAwareness;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
@@ -67,7 +69,7 @@
AdditionalSubtypeUtils.save(map, inputMethodMap, userId);
}
- static void initialize(@NonNull Handler handler) {
+ static void initialize(@NonNull Handler handler, @NonNull Context context) {
final UserManagerInternal userManagerInternal =
LocalServices.getService(UserManagerInternal.class);
handler.post(() -> {
@@ -79,8 +81,16 @@
handler.post(() -> {
synchronized (ImfLock.class) {
if (!sPerUserMap.contains(userId)) {
- sPerUserMap.put(userId,
- AdditionalSubtypeUtils.load(userId));
+ final AdditionalSubtypeMap additionalSubtypeMap =
+ AdditionalSubtypeUtils.load(userId);
+ sPerUserMap.put(userId, additionalSubtypeMap);
+ final InputMethodSettings settings =
+ InputMethodManagerService
+ .queryInputMethodServicesInternal(context,
+ userId,
+ additionalSubtypeMap,
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, settings);
}
}
});
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index a100fe0..29cccdf 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -74,7 +74,6 @@
@GuardedBy("ImfLock.class") @Nullable private IInputMethodInvoker mCurMethod;
@GuardedBy("ImfLock.class") private int mCurMethodUid = Process.INVALID_UID;
@GuardedBy("ImfLock.class") @Nullable private IBinder mCurToken;
- @GuardedBy("ImfLock.class") private int mCurSeq;
@GuardedBy("ImfLock.class") private boolean mVisibleBound;
@GuardedBy("ImfLock.class") private boolean mSupportsStylusHw;
@GuardedBy("ImfLock.class") private boolean mSupportsConnectionlessStylusHw;
@@ -195,27 +194,6 @@
}
/**
- * The current binding sequence number, incremented every time there is
- * a new bind performed.
- */
- @GuardedBy("ImfLock.class")
- int getSequenceNumber() {
- return mCurSeq;
- }
-
- /**
- * Increase the current binding sequence number by one.
- * Reset to 1 on overflow.
- */
- @GuardedBy("ImfLock.class")
- void advanceSequenceNumber() {
- mCurSeq += 1;
- if (mCurSeq <= 0) {
- mCurSeq = 1;
- }
- }
-
- /**
* If non-null, this is the input method service we are currently connected
* to.
*/
@@ -435,9 +413,11 @@
mLastBindTime = SystemClock.uptimeMillis();
addFreshWindowToken();
+ final UserData monitor = UserData.getOrCreate(
+ mService.getCurrentImeUserIdLocked());
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
- null, null, null, mCurId, mCurSeq, false);
+ null, null, null, mCurId, monitor.mSequence.getSequenceNumber(), false);
}
Slog.w(InputMethodManagerService.TAG,
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index fef5661..d0a83a6 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -284,9 +284,12 @@
final Context mContext;
final Resources mRes;
private final Handler mHandler;
- @NonNull
+
@MultiUserUnawareField
- private InputMethodSettings mSettings;
+ @UserIdInt
+ @GuardedBy("ImfLock.class")
+ private int mCurrentUserId;
+
@MultiUserUnawareField
final SettingsObserver mSettingsObserver;
final WindowManagerInternal mWindowManagerInternal;
@@ -476,7 +479,8 @@
*/
@GuardedBy("ImfLock.class")
private int getSequenceNumberLocked() {
- return mBindingController.getSequenceNumber();
+ final UserData monitor = UserData.getOrCreate(mCurrentUserId);
+ return monitor.mSequence.getSequenceNumber();
}
/**
@@ -485,13 +489,14 @@
*/
@GuardedBy("ImfLock.class")
private void advanceSequenceNumberLocked() {
- mBindingController.advanceSequenceNumber();
+ final UserData monitor = UserData.getOrCreate(mCurrentUserId);
+ monitor.mSequence.advanceSequenceNumber();
}
@GuardedBy("ImfLock.class")
@Nullable
InputMethodInfo queryInputMethodForCurrentUserLocked(@NonNull String imeId) {
- return mSettings.getMethodMap().get(imeId);
+ return InputMethodSettingsRepository.get(mCurrentUserId).getMethodMap().get(imeId);
}
/**
@@ -555,10 +560,13 @@
private InputMethodSubtype mCurrentSubtype;
/**
- * {@code true} if the IME has not been mostly hidden via {@link android.view.InsetsController}
+ * Map of window perceptible states indexed by their associated window tokens.
+ *
+ * The value {@code true} indicates that IME has not been mostly hidden via
+ * {@link android.view.InsetsController} for the given window.
*/
- @MultiUserUnawareField
- private boolean mCurPerceptible;
+ @GuardedBy("ImfLock.class")
+ private final WeakHashMap<IBinder, Boolean> mFocusedWindowPerceptible = new WeakHashMap<>();
/**
* Set to true if our ServiceConnection is currently actively bound to
@@ -809,7 +817,8 @@
InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
} else {
boolean enabledChanged = false;
- String newEnabled = mSettings.getEnabledInputMethodsStr();
+ String newEnabled = InputMethodSettingsRepository.get(mCurrentUserId)
+ .getEnabledInputMethodsStr();
if (!mLastEnabled.equals(newEnabled)) {
mLastEnabled = newEnabled;
enabledChanged = true;
@@ -841,9 +850,11 @@
// sender userId can be a real user ID or USER_ALL.
final int senderUserId = pendingResult.getSendingUserId();
if (senderUserId != UserHandle.USER_ALL) {
- if (senderUserId != mSettings.getUserId()) {
- // A background user is trying to hide the dialog. Ignore.
- return;
+ synchronized (ImfLock.class) {
+ if (senderUserId != mCurrentUserId) {
+ // A background user is trying to hide the dialog. Ignore.
+ return;
+ }
}
}
mMenuController.hideInputMethodMenu();
@@ -867,7 +878,15 @@
if (!mSystemReady) {
return;
}
- buildInputMethodListLocked(true);
+ for (int userId : mUserManagerInternal.getUserIds()) {
+ final InputMethodSettings settings = queryInputMethodServicesInternal(
+ mContext,
+ userId,
+ AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, settings);
+ }
+ postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */);
// If the locale is changed, needs to reset the default ime
resetDefaultImeLocked(mContext);
updateFromSettingsLocked(true);
@@ -927,7 +946,7 @@
@GuardedBy("ImfLock.class")
private boolean isChangingPackagesOfCurrentUserLocked() {
final int userId = getChangingUserId();
- final boolean retval = userId == mSettings.getUserId();
+ final boolean retval = userId == mCurrentUserId;
if (DEBUG) {
if (!retval) {
Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
@@ -942,8 +961,10 @@
if (!isChangingPackagesOfCurrentUserLocked()) {
return false;
}
- String curInputMethodId = mSettings.getSelectedInputMethod();
- final List<InputMethodInfo> methodList = mSettings.getMethodList();
+ final InputMethodSettings settings =
+ InputMethodSettingsRepository.get(mCurrentUserId);
+ String curInputMethodId = settings.getSelectedInputMethod();
+ final List<InputMethodInfo> methodList = settings.getMethodList();
final int numImes = methodList.size();
if (curInputMethodId != null) {
for (int i = 0; i < numImes; i++) {
@@ -1060,16 +1081,10 @@
private void onFinishPackageChangesInternal() {
synchronized (ImfLock.class) {
final int userId = getChangingUserId();
- final boolean isCurrentUser = (userId == mSettings.getUserId());
+ final boolean isCurrentUser = (userId == mCurrentUserId);
final AdditionalSubtypeMap additionalSubtypeMap =
AdditionalSubtypeMapRepository.get(userId);
- final InputMethodSettings settings;
- if (isCurrentUser) {
- settings = mSettings;
- } else {
- settings = queryInputMethodServicesInternal(mContext, userId,
- additionalSubtypeMap, DirectBootAwareness.AUTO);
- }
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
InputMethodInfo curIm = null;
String curInputMethodId = settings.getSelectedInputMethod();
@@ -1113,13 +1128,18 @@
AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
settings.getMethodMap());
}
-
- if (!isCurrentUser
- || !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
+ if (isCurrentUser
+ && !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
return;
}
- buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ userId, newAdditionalSubtypeMap, DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, newSettings);
+ if (!isCurrentUser) {
+ return;
+ }
+ postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
boolean changed = false;
@@ -1271,17 +1291,18 @@
void onUnlockUser(@UserIdInt int userId) {
synchronized (ImfLock.class) {
- final int currentUserId = mSettings.getUserId();
if (DEBUG) {
- Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId);
+ Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + mCurrentUserId);
}
- if (userId != currentUserId) {
+ if (!mSystemReady) {
return;
}
- mSettings = InputMethodSettings.createEmptyMap(userId);
- if (mSystemReady) {
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, newSettings);
+ if (mCurrentUserId == userId) {
// We need to rebuild IMEs.
- buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+ postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
}
}
@@ -1347,19 +1368,21 @@
mShowOngoingImeSwitcherForPhones = false;
- AdditionalSubtypeMapRepository.initialize(mHandler);
+ // InputMethodSettingsRepository should be initialized before buildInputMethodListLocked
+ InputMethodSettingsRepository.initialize(mHandler, mContext);
+ AdditionalSubtypeMapRepository.initialize(mHandler, mContext);
+ UserData.initialize(mHandler);
- final int userId = mActivityManagerInternal.getCurrentUserId();
+ mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
- // mSettings should be created before buildInputMethodListLocked
- mSettings = InputMethodSettings.createEmptyMap(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
mSwitchingController =
InputMethodSubtypeSwitchingController.createInstanceLocked(context,
- mSettings.getMethodMap(), userId);
+ settings.getMethodMap(), settings.getUserId());
mHardwareKeyboardShortcutController =
- new HardwareKeyboardShortcutController(mSettings.getMethodMap(),
- mSettings.getUserId());
+ new HardwareKeyboardShortcutController(settings.getMethodMap(),
+ settings.getUserId());
mMenuController = new InputMethodMenuController(this);
mBindingController =
bindingControllerForTesting != null
@@ -1392,7 +1415,7 @@
@GuardedBy("ImfLock.class")
@UserIdInt
int getCurrentImeUserIdLocked() {
- return mSettings.getUserId();
+ return mCurrentUserId;
}
private final class InkWindowInitializer implements Runnable {
@@ -1428,12 +1451,13 @@
private void resetDefaultImeLocked(Context context) {
// Do not reset the default (current) IME when it is a 3rd-party IME
String selectedMethodId = getSelectedMethodIdLocked();
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
if (selectedMethodId != null
- && !mSettings.getMethodMap().get(selectedMethodId).isSystem()) {
+ && !settings.getMethodMap().get(selectedMethodId).isSystem()) {
return;
}
final List<InputMethodInfo> suitableImes = InputMethodInfoUtils.getDefaultEnabledImes(
- context, mSettings.getEnabledInputMethodList());
+ context, settings.getEnabledInputMethodList());
if (suitableImes.isEmpty()) {
Slog.i(TAG, "No default found");
return;
@@ -1489,7 +1513,7 @@
IInputMethodClientInvoker clientToBeReset) {
if (DEBUG) {
Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId
- + " currentUserId=" + mSettings.getUserId());
+ + " currentUserId=" + mCurrentUserId);
}
maybeInitImeNavbarConfigLocked(newUserId);
@@ -1497,8 +1521,9 @@
// ContentObserver should be registered again when the user is changed
mSettingsObserver.registerContentObserverLocked(newUserId);
- mSettings = InputMethodSettings.createEmptyMap(newUserId);
- final String defaultImiId = mSettings.getSelectedInputMethod();
+ mCurrentUserId = newUserId;
+ final String defaultImiId = SecureSettingsWrapper.getString(
+ Settings.Secure.DEFAULT_INPUT_METHOD, null, newUserId);
if (DEBUG) {
Slog.d(TAG, "Switching user stage 2/3. newUserId=" + newUserId
@@ -1515,8 +1540,10 @@
// The mSystemReady flag is set during boot phase,
// and user switch would not happen at that time.
resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_USER);
- buildInputMethodListLocked(initialUserSwitch);
- if (TextUtils.isEmpty(mSettings.getSelectedInputMethod())) {
+
+ final InputMethodSettings newSettings = InputMethodSettingsRepository.get(newUserId);
+ postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */);
+ if (TextUtils.isEmpty(newSettings.getSelectedInputMethod())) {
// This is the first time of the user switch and
// set the current ime to the proper one.
resetDefaultImeLocked(mContext);
@@ -1526,12 +1553,12 @@
if (initialUserSwitch) {
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
getPackageManagerForUser(mContext, newUserId),
- mSettings.getEnabledInputMethodList());
+ newSettings.getEnabledInputMethodList());
}
if (DEBUG) {
Slog.d(TAG, "Switching user stage 3/3. newUserId=" + newUserId
- + " selectedIme=" + mSettings.getSelectedInputMethod());
+ + " selectedIme=" + newSettings.getSelectedInputMethod());
}
if (mIsInteractive && clientToBeReset != null) {
@@ -1554,7 +1581,7 @@
}
if (!mSystemReady) {
mSystemReady = true;
- final int currentUserId = mSettings.getUserId();
+ final int currentUserId = mCurrentUserId;
mStatusBarManagerInternal =
LocalServices.getService(StatusBarManagerInternal.class);
hideStatusBarIconLocked();
@@ -1575,7 +1602,7 @@
// the "mImeDrawsImeNavBarResLazyInitFuture" field.
synchronized (ImfLock.class) {
mImeDrawsImeNavBarResLazyInitFuture = null;
- if (currentUserId != mSettings.getUserId()) {
+ if (currentUserId != mCurrentUserId) {
// This means that the current user is already switched to other user
// before the background task is executed. In this scenario the relevant
// field should already be initialized.
@@ -1594,13 +1621,19 @@
UserHandle.ALL, broadcastFilterForAllUsers, null, null,
Context.RECEIVER_EXPORTED);
- final String defaultImiId = mSettings.getSelectedInputMethod();
+ final String defaultImiId = SecureSettingsWrapper.getString(
+ Settings.Secure.DEFAULT_INPUT_METHOD, null, currentUserId);
final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
- buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */);
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ currentUserId, AdditionalSubtypeMapRepository.get(currentUserId),
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(currentUserId, newSettings);
+ postInputMethodSettingUpdatedLocked(
+ !imeSelectedOnBoot /* resetDefaultEnabledIme */);
updateFromSettingsLocked(true);
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
getPackageManagerForUser(mContext, currentUserId),
- mSettings.getEnabledInputMethodList());
+ newSettings.getEnabledInputMethodList());
}
}
}
@@ -1647,7 +1680,7 @@
}
synchronized (ImfLock.class) {
final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
- mSettings.getUserId(), null);
+ mCurrentUserId, null);
if (resolvedUserIds.length != 1) {
return Collections.emptyList();
}
@@ -1670,7 +1703,7 @@
}
synchronized (ImfLock.class) {
final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
- mSettings.getUserId(), null);
+ mCurrentUserId, null);
if (resolvedUserIds.length != 1) {
return Collections.emptyList();
}
@@ -1698,14 +1731,12 @@
}
// Check if selected IME of current user supports handwriting.
- if (userId == mSettings.getUserId()) {
+ if (userId == mCurrentUserId) {
return mBindingController.supportsStylusHandwriting()
&& (!connectionless
|| mBindingController.supportsConnectionlessStylusHandwriting());
}
- //TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList.
- //TODO(b/210039666): use cache.
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final InputMethodInfo imi = settings.getMethodMap().get(
settings.getSelectedInputMethod());
return imi != null && imi.supportsStylusHandwriting()
@@ -1729,9 +1760,8 @@
private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId,
@DirectBootAwareness int directBootAwareness, int callingUid) {
final InputMethodSettings settings;
- if (userId == mSettings.getUserId()
- && directBootAwareness == DirectBootAwareness.AUTO) {
- settings = mSettings;
+ if (directBootAwareness == DirectBootAwareness.AUTO) {
+ settings = InputMethodSettingsRepository.get(userId);
} else {
final AdditionalSubtypeMap additionalSubtypeMap =
AdditionalSubtypeMapRepository.get(userId);
@@ -1749,15 +1779,8 @@
@GuardedBy("ImfLock.class")
private List<InputMethodInfo> getEnabledInputMethodListLocked(@UserIdInt int userId,
int callingUid) {
- final ArrayList<InputMethodInfo> methodList;
- final InputMethodSettings settings;
- if (userId == mSettings.getUserId()) {
- methodList = mSettings.getEnabledInputMethodList();
- settings = mSettings;
- } else {
- settings = queryMethodMapForUserLocked(userId);
- methodList = settings.getEnabledInputMethodList();
- }
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+ final ArrayList<InputMethodInfo> methodList = settings.getEnabledInputMethodList();
// filter caller's access to input methods
methodList.removeIf(imi ->
!canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings));
@@ -1815,22 +1838,7 @@
@GuardedBy("ImfLock.class")
private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId,
boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId, int callingUid) {
- if (userId == mSettings.getUserId()) {
- final InputMethodInfo imi;
- String selectedMethodId = getSelectedMethodIdLocked();
- if (imiId == null && selectedMethodId != null) {
- imi = mSettings.getMethodMap().get(selectedMethodId);
- } else {
- imi = mSettings.getMethodMap().get(imiId);
- }
- if (imi == null || !canCallerAccessInputMethod(
- imi.getPackageName(), callingUid, userId, mSettings)) {
- return Collections.emptyList();
- }
- return mSettings.getEnabledInputMethodSubtypeList(
- imi, allowsImplicitlyEnabledSubtypes);
- }
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final InputMethodInfo imi = settings.getMethodMap().get(imiId);
if (imi == null) {
return Collections.emptyList();
@@ -2010,7 +2018,7 @@
final boolean restarting = !initial;
final Binder startInputToken = new Binder();
- final StartInputInfo info = new StartInputInfo(mSettings.getUserId(),
+ final StartInputInfo info = new StartInputInfo(mCurrentUserId,
getCurTokenLocked(),
mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting,
UserHandle.getUserId(mCurClient.mUid),
@@ -2024,9 +2032,9 @@
// same-user scenarios.
// That said ignoring cross-user scenario will never affect IMEs that do not have
// INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case.
- if (mSettings.getUserId() == UserHandle.getUserId(
+ if (mCurrentUserId == UserHandle.getUserId(
mCurClient.mUid)) {
- mPackageManagerInternal.grantImplicitAccess(mSettings.getUserId(),
+ mPackageManagerInternal.grantImplicitAccess(mCurrentUserId,
null /* intent */, UserHandle.getAppId(getCurMethodUidLocked()),
mCurClient.mUid, true /* direct */);
}
@@ -2049,7 +2057,8 @@
}
String curId = getCurIdLocked();
- final InputMethodInfo curInputMethodInfo = mSettings.getMethodMap().get(curId);
+ final InputMethodInfo curInputMethodInfo = InputMethodSettingsRepository.get(mCurrentUserId)
+ .getMethodMap().get(curId);
final boolean suppressesSpellChecker =
curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions =
@@ -2229,17 +2238,18 @@
return currentMethodId;
}
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
final int oldDeviceId = mDeviceIdToShowIme;
mDeviceIdToShowIme = mVdmInternal.getDeviceIdForDisplayId(mDisplayIdToShowIme);
if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
if (oldDeviceId == DEVICE_ID_DEFAULT) {
return currentMethodId;
}
- final String defaultDeviceMethodId = mSettings.getSelectedDefaultDeviceInputMethod();
+ final String defaultDeviceMethodId = settings.getSelectedDefaultDeviceInputMethod();
if (DEBUG) {
Slog.v(TAG, "Restoring default device input method: " + defaultDeviceMethodId);
}
- mSettings.putSelectedDefaultDeviceInputMethod(null);
+ settings.putSelectedDefaultDeviceInputMethod(null);
return defaultDeviceMethodId;
}
@@ -2247,7 +2257,7 @@
mVirtualDeviceMethodMap.get(mDeviceIdToShowIme, currentMethodId);
if (Objects.equals(deviceMethodId, currentMethodId)) {
return currentMethodId;
- } else if (!mSettings.getMethodMap().containsKey(deviceMethodId)) {
+ } else if (!settings.getMethodMap().containsKey(deviceMethodId)) {
if (DEBUG) {
Slog.v(TAG, "Disabling IME on virtual device with id " + mDeviceIdToShowIme
+ " because its custom input method is not available: " + deviceMethodId);
@@ -2259,7 +2269,7 @@
if (DEBUG) {
Slog.v(TAG, "Storing default device input method " + currentMethodId);
}
- mSettings.putSelectedDefaultDeviceInputMethod(currentMethodId);
+ settings.putSelectedDefaultDeviceInputMethod(currentMethodId);
}
if (DEBUG) {
Slog.v(TAG, "Switching current input method from " + currentMethodId
@@ -2289,7 +2299,8 @@
if (isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags)) {
return false;
}
- final InputMethodInfo imi = mSettings.getMethodMap().get(selectedMethodId);
+ final InputMethodInfo imi = InputMethodSettingsRepository.get(mCurrentUserId)
+ .getMethodMap().get(selectedMethodId);
if (imi == null) {
return false;
}
@@ -2633,7 +2644,7 @@
} else if (packageName != null) {
if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
final PackageManager userAwarePackageManager =
- getPackageManagerForUser(mContext, mSettings.getUserId());
+ getPackageManagerForUser(mContext, mCurrentUserId);
ApplicationInfo applicationInfo = null;
try {
applicationInfo = userAwarePackageManager.getApplicationInfo(packageName,
@@ -2695,7 +2706,7 @@
return false;
}
if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
- && mWindowManagerInternal.isKeyguardSecure(mSettings.getUserId())) {
+ && mWindowManagerInternal.isKeyguardSecure(mCurrentUserId)) {
return false;
}
if ((visibility & InputMethodService.IME_ACTIVE) == 0
@@ -2712,7 +2723,8 @@
return false;
}
- List<InputMethodInfo> imes = mSettings.getEnabledInputMethodListWithFilter(
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ List<InputMethodInfo> imes = settings.getEnabledInputMethodListWithFilter(
InputMethodInfo::shouldShowInInputMethodPicker);
final int numImes = imes.size();
if (numImes > 2) return true;
@@ -2724,7 +2736,7 @@
for (int i = 0; i < numImes; ++i) {
final InputMethodInfo imi = imes.get(i);
final List<InputMethodSubtype> subtypes =
- mSettings.getEnabledInputMethodSubtypeList(imi, true);
+ settings.getEnabledInputMethodSubtypeList(imi, true);
final int subtypeCount = subtypes.size();
if (subtypeCount == 0) {
++nonAuxCount;
@@ -2835,12 +2847,16 @@
+ " inv: " + (vis & InputMethodService.IME_INVISIBLE)
+ " displayId: " + mCurTokenDisplayId);
}
+ final IBinder focusedWindowToken = mImeBindingState != null
+ ? mImeBindingState.mFocusedWindow : null;
+ final Boolean windowPerceptible = focusedWindowToken != null
+ ? mFocusedWindowPerceptible.get(focusedWindowToken) : null;
// TODO: Move this clearing calling identity block to setImeWindowStatus after making sure
// all updateSystemUi happens on system privilege.
final long ident = Binder.clearCallingIdentity();
try {
- if (!mCurPerceptible) {
+ if (windowPerceptible != null && !windowPerceptible) {
if ((vis & InputMethodService.IME_VISIBLE) != 0) {
vis &= ~InputMethodService.IME_VISIBLE;
vis |= InputMethodService.IME_VISIBLE_IMPERCEPTIBLE;
@@ -2872,11 +2888,12 @@
@GuardedBy("ImfLock.class")
void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
if (enabledMayChange) {
final PackageManager userAwarePackageManager = getPackageManagerForUser(mContext,
- mSettings.getUserId());
+ settings.getUserId());
- List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodList();
+ List<InputMethodInfo> enabled = settings.getEnabledInputMethodList();
for (int i = 0; i < enabled.size(); i++) {
// We allow the user to select "disabled until used" apps, so if they
// are enabling one of those here we now need to make it enabled.
@@ -2903,20 +2920,20 @@
if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
String ime = SecureSettingsWrapper.getString(
- Settings.Secure.DEFAULT_INPUT_METHOD, null, mSettings.getUserId());
+ Settings.Secure.DEFAULT_INPUT_METHOD, null, settings.getUserId());
String defaultDeviceIme = SecureSettingsWrapper.getString(
- Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
+ Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, settings.getUserId());
if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
if (DEBUG) {
Slog.v(TAG, "Current input method " + ime + " differs from the stored default"
- + " device input method for user " + mSettings.getUserId()
+ + " device input method for user " + settings.getUserId()
+ " - restoring " + defaultDeviceIme);
}
SecureSettingsWrapper.putString(
Settings.Secure.DEFAULT_INPUT_METHOD, defaultDeviceIme,
- mSettings.getUserId());
+ settings.getUserId());
SecureSettingsWrapper.putString(
- Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
+ Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, settings.getUserId());
}
}
@@ -2924,14 +2941,14 @@
// ENABLED_INPUT_METHODS is taking care of keeping them correctly in
// sync, so we will never have a DEFAULT_INPUT_METHOD that is not
// enabled.
- String id = mSettings.getSelectedInputMethod();
+ String id = settings.getSelectedInputMethod();
// There is no input method selected, try to choose new applicable input method.
if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
- id = mSettings.getSelectedInputMethod();
+ id = settings.getSelectedInputMethod();
}
if (!TextUtils.isEmpty(id)) {
try {
- setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
+ setInputMethodLocked(id, settings.getSelectedInputMethodSubtypeId(id));
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Unknown input method from prefs: " + id, e);
resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_IME_FAILED);
@@ -2942,18 +2959,18 @@
}
// TODO: Instantiate mSwitchingController for each user.
- if (mSettings.getUserId() == mSwitchingController.getUserId()) {
- mSwitchingController.resetCircularListLocked(mSettings.getMethodMap());
+ if (settings.getUserId() == mSwitchingController.getUserId()) {
+ mSwitchingController.resetCircularListLocked(settings.getMethodMap());
} else {
mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
- mContext, mSettings.getMethodMap(), mSettings.getUserId());
+ mContext, settings.getMethodMap(), settings.getUserId());
}
// TODO: Instantiate mHardwareKeyboardShortcutController for each user.
- if (mSettings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
- mHardwareKeyboardShortcutController.reset(mSettings.getMethodMap());
+ if (settings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
+ mHardwareKeyboardShortcutController.reset(settings.getMethodMap());
} else {
mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
- mSettings.getMethodMap(), mSettings.getUserId());
+ settings.getMethodMap(), settings.getUserId());
}
sendOnNavButtonFlagsChangedLocked();
}
@@ -2977,14 +2994,15 @@
@GuardedBy("ImfLock.class")
void setInputMethodLocked(String id, int subtypeId, int deviceId) {
- InputMethodInfo info = mSettings.getMethodMap().get(id);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ InputMethodInfo info = settings.getMethodMap().get(id);
if (info == null) {
throw getExceptionForUnknownImeId(id);
}
// See if we need to notify a subtype change within the same IME.
if (id.equals(getSelectedMethodIdLocked())) {
- final int userId = mSettings.getUserId();
+ final int userId = settings.getUserId();
final int subtypeCount = info.getSubtypeCount();
if (subtypeCount <= 0) {
notifyInputMethodSubtypeChangedLocked(userId, info, null);
@@ -3025,7 +3043,7 @@
// method is a custom one specific to a virtual device. So only update the settings
// entry used to restore the default device input method once we want to show the IME
// back on the default device.
- mSettings.putSelectedDefaultDeviceInputMethod(id);
+ settings.putSelectedDefaultDeviceInputMethod(id);
return;
}
IInputMethodInvoker curMethod = getCurMethodLocked();
@@ -3305,11 +3323,12 @@
Binder.withCleanCallingIdentity(() -> {
Objects.requireNonNull(windowToken, "windowToken must not be null");
synchronized (ImfLock.class) {
+ Boolean windowPerceptible = mFocusedWindowPerceptible.get(windowToken);
if (mImeBindingState.mFocusedWindow != windowToken
- || mCurPerceptible == perceptible) {
+ || (windowPerceptible != null && windowPerceptible == perceptible)) {
return;
}
- mCurPerceptible = perceptible;
+ mFocusedWindowPerceptible.put(windowToken, windowPerceptible);
updateSystemUiLocked();
}
});
@@ -3535,7 +3554,7 @@
return InputBindResult.USER_SWITCHING;
}
final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds(
- mSettings.getUserId(), false /* enabledOnly */);
+ mCurrentUserId, false /* enabledOnly */);
for (int profileId : profileIdsWithDisabled) {
if (profileId == userId) {
scheduleSwitchUserTaskLocked(userId, cs.mClient);
@@ -3581,10 +3600,10 @@
}
// Verify if caller is a background user.
- final int currentUserId = mSettings.getUserId();
- if (userId != currentUserId) {
+ if (userId != mCurrentUserId) {
if (ArrayUtils.contains(
- mUserManagerInternal.getProfileIds(currentUserId, false), userId)) {
+ mUserManagerInternal.getProfileIds(mCurrentUserId, false),
+ userId)) {
// cross-profile access is always allowed here to allow
// profile-switching.
scheduleSwitchUserTaskLocked(userId, cs.mClient);
@@ -3687,7 +3706,7 @@
}
mImeBindingState = new ImeBindingState(windowToken, softInputMode, cs, editorInfo);
- mCurPerceptible = true;
+ mFocusedWindowPerceptible.put(windowToken, true);
// We want to start input before showing the IME, but after closing
// it. We want to do this after closing it to help the IME disappear
@@ -3773,7 +3792,7 @@
&& mImeBindingState.mFocusedWindowClient.mClient.asBinder() == client.asBinder()) {
return true;
}
- if (mSettings.getUserId() != UserHandle.getUserId(uid)) {
+ if (mCurrentUserId != UserHandle.getUserId(uid)) {
return false;
}
if (getCurIntentLocked() != null && InputMethodUtils.checkIfPackageBelongsToUid(
@@ -3841,9 +3860,10 @@
if (!calledWithValidTokenLocked(token)) {
return;
}
- final InputMethodInfo imi = mSettings.getMethodMap().get(id);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ final InputMethodInfo imi = settings.getMethodMap().get(id);
if (imi == null || !canCallerAccessInputMethod(
- imi.getPackageName(), callingUid, userId, mSettings)) {
+ imi.getPackageName(), callingUid, userId, settings)) {
throw getExceptionForUnknownImeId(id);
}
setInputMethodWithSubtypeIdLocked(token, id, NOT_A_SUBTYPE_ID);
@@ -3859,9 +3879,10 @@
if (!calledWithValidTokenLocked(token)) {
return;
}
- final InputMethodInfo imi = mSettings.getMethodMap().get(id);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ final InputMethodInfo imi = settings.getMethodMap().get(id);
if (imi == null || !canCallerAccessInputMethod(
- imi.getPackageName(), callingUid, userId, mSettings)) {
+ imi.getPackageName(), callingUid, userId, settings)) {
throw getExceptionForUnknownImeId(id);
}
if (subtype != null) {
@@ -3879,10 +3900,11 @@
if (!calledWithValidTokenLocked(token)) {
return false;
}
- final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtype();
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ final Pair<String, String> lastIme = settings.getLastInputMethodAndSubtype();
final InputMethodInfo lastImi;
if (lastIme != null) {
- lastImi = mSettings.getMethodMap().get(lastIme.first);
+ lastImi = settings.getMethodMap().get(lastIme.first);
} else {
lastImi = null;
}
@@ -3906,7 +3928,7 @@
// This is a safety net. If the currentSubtype can't be added to the history
// and the framework couldn't find the last ime, we will make the last ime be
// the most applicable enabled keyboard subtype of the system imes.
- final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodList();
+ final List<InputMethodInfo> enabled = settings.getEnabledInputMethodList();
if (enabled != null) {
final int enabledCount = enabled.size();
final String locale;
@@ -3914,7 +3936,7 @@
&& !TextUtils.isEmpty(mCurrentSubtype.getLocale())) {
locale = mCurrentSubtype.getLocale();
} else {
- locale = SystemLocaleWrapper.get(mSettings.getUserId()).get(0).toString();
+ locale = SystemLocaleWrapper.get(mCurrentUserId).get(0).toString();
}
for (int i = 0; i < enabledCount; ++i) {
final InputMethodInfo imi = enabled.get(i);
@@ -3961,8 +3983,9 @@
@GuardedBy("ImfLock.class")
private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme) {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
- onlyCurrentIme, mSettings.getMethodMap().get(getSelectedMethodIdLocked()),
+ onlyCurrentIme, settings.getMethodMap().get(getSelectedMethodIdLocked()),
mCurrentSubtype);
if (nextSubtype == null) {
return false;
@@ -3978,9 +4001,10 @@
if (!calledWithValidTokenLocked(token)) {
return false;
}
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
false /* onlyCurrentIme */,
- mSettings.getMethodMap().get(getSelectedMethodIdLocked()), mCurrentSubtype);
+ settings.getMethodMap().get(getSelectedMethodIdLocked()), mCurrentSubtype);
return nextSubtype != null;
}
}
@@ -3992,12 +4016,7 @@
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
synchronized (ImfLock.class) {
- if (mSettings.getUserId() == userId) {
- return mSettings.getLastInputMethodSubtype();
- }
-
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
- return settings.getLastInputMethodSubtype();
+ return InputMethodSettingsRepository.get(userId).getLastInputMethodSubtype();
}
}
@@ -4028,20 +4047,21 @@
}
final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
- final boolean isCurrentUser = (mSettings.getUserId() == userId);
- final InputMethodSettings settings = isCurrentUser
- ? mSettings
- : queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
- DirectBootAwareness.AUTO);
+ final boolean isCurrentUser = (mCurrentUserId == userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final var newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap(
imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid);
if (additionalSubtypeMap != newAdditionalSubtypeMap) {
AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
settings.getMethodMap());
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ userId, AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, newSettings);
if (isCurrentUser) {
final long ident = Binder.clearCallingIdentity();
try {
- buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+ postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -4070,9 +4090,8 @@
final long ident = Binder.clearCallingIdentity();
try {
synchronized (ImfLock.class) {
- final boolean currentUser = (mSettings.getUserId() == userId);
- final InputMethodSettings settings = currentUser
- ? mSettings : queryMethodMapForUserLocked(userId);
+ final boolean currentUser = (mCurrentUserId == userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) {
return;
}
@@ -4422,11 +4441,11 @@
}
return;
}
- if (mSettings.getUserId() != mSwitchingController.getUserId()) {
+ if (mCurrentUserId != mSwitchingController.getUserId()) {
return;
}
- final InputMethodInfo imi =
- mSettings.getMethodMap().get(getSelectedMethodIdLocked());
+ final InputMethodInfo imi = InputMethodSettingsRepository.get(mCurrentUserId)
+ .getMethodMap().get(getSelectedMethodIdLocked());
if (imi != null) {
mSwitchingController.onUserActionLocked(imi, mCurrentSubtype);
}
@@ -4486,8 +4505,9 @@
return;
} else {
// Called with current IME's token.
- if (mSettings.getMethodMap().get(id) != null
- && mSettings.getEnabledInputMethodListWithFilter(
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ if (settings.getMethodMap().get(id) != null
+ && settings.getEnabledInputMethodListWithFilter(
(info) -> info.getId().equals(id)).isEmpty()) {
throw new IllegalStateException("Requested IME is not enabled: " + id);
}
@@ -4666,21 +4686,23 @@
return false;
}
synchronized (ImfLock.class) {
+ final InputMethodSettings settings =
+ InputMethodSettingsRepository.get(mCurrentUserId);
final boolean isScreenLocked = mWindowManagerInternal.isKeyguardLocked()
- && mWindowManagerInternal.isKeyguardSecure(mSettings.getUserId());
- final String lastInputMethodId = mSettings.getSelectedInputMethod();
+ && mWindowManagerInternal.isKeyguardSecure(settings.getUserId());
+ final String lastInputMethodId = settings.getSelectedInputMethod();
int lastInputMethodSubtypeId =
- mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
+ settings.getSelectedInputMethodSubtypeId(lastInputMethodId);
final List<ImeSubtypeListItem> imList = InputMethodSubtypeSwitchingController
.getSortedInputMethodAndSubtypeList(
showAuxSubtypes, isScreenLocked, true /* forImeMenu */,
- mContext, mSettings.getMethodMap(), mSettings.getUserId());
+ mContext, settings.getMethodMap(), settings.getUserId());
if (imList.isEmpty()) {
Slog.w(TAG, "Show switching menu failed, imList is empty,"
+ " showAuxSubtypes: " + showAuxSubtypes
+ " isScreenLocked: " + isScreenLocked
- + " userId: " + mSettings.getUserId());
+ + " userId: " + settings.getUserId());
return false;
}
@@ -4866,8 +4888,9 @@
@GuardedBy("ImfLock.class")
private boolean chooseNewDefaultIMELocked() {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
final InputMethodInfo imi = InputMethodInfoUtils.getMostApplicableDefaultIME(
- mSettings.getEnabledInputMethodList());
+ settings.getEnabledInputMethodList());
if (imi != null) {
if (DEBUG) {
Slog.d(TAG, "New default IME was selected: " + imi.getId());
@@ -4969,7 +4992,7 @@
}
@GuardedBy("ImfLock.class")
- void buildInputMethodListLocked(boolean resetDefaultEnabledIme) {
+ void postInputMethodSettingUpdatedLocked(boolean resetDefaultEnabledIme) {
if (DEBUG) {
Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
+ " \n ------ caller=" + Debug.getCallers(10));
@@ -4981,9 +5004,7 @@
mMethodMapUpdateCount++;
mMyPackageMonitor.clearKnownImePackageNamesLocked();
- mSettings = queryInputMethodServicesInternal(mContext, mSettings.getUserId(),
- AdditionalSubtypeMapRepository.get(mSettings.getUserId()),
- DirectBootAwareness.AUTO);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
// Construct the set of possible IME packages for onPackageChanged() to avoid false
// negatives when the package state remains to be the same but only the component state is
@@ -4995,7 +5016,7 @@
final List<ResolveInfo> allInputMethodServices =
mContext.getPackageManager().queryIntentServicesAsUser(
new Intent(InputMethod.SERVICE_INTERFACE),
- PackageManager.MATCH_DISABLED_COMPONENTS, mSettings.getUserId());
+ PackageManager.MATCH_DISABLED_COMPONENTS, settings.getUserId());
final int numImes = allInputMethodServices.size();
for (int i = 0; i < numImes; ++i) {
final ServiceInfo si = allInputMethodServices.get(i).serviceInfo;
@@ -5010,11 +5031,11 @@
if (!resetDefaultEnabledIme) {
boolean enabledImeFound = false;
boolean enabledNonAuxImeFound = false;
- final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodList();
+ final List<InputMethodInfo> enabledImes = settings.getEnabledInputMethodList();
final int numImes = enabledImes.size();
for (int i = 0; i < numImes; ++i) {
final InputMethodInfo imi = enabledImes.get(i);
- if (mSettings.getMethodMap().containsKey(imi.getId())) {
+ if (settings.getMethodMap().containsKey(imi.getId())) {
enabledImeFound = true;
if (!imi.isAuxiliaryIme()) {
enabledNonAuxImeFound = true;
@@ -5038,7 +5059,7 @@
if (resetDefaultEnabledIme || reenableMinimumNonAuxSystemImes) {
final ArrayList<InputMethodInfo> defaultEnabledIme =
- InputMethodInfoUtils.getDefaultEnabledImes(mContext, mSettings.getMethodList(),
+ InputMethodInfoUtils.getDefaultEnabledImes(mContext, settings.getMethodList(),
reenableMinimumNonAuxSystemImes);
final int numImes = defaultEnabledIme.size();
for (int i = 0; i < numImes; ++i) {
@@ -5050,9 +5071,9 @@
}
}
- final String defaultImiId = mSettings.getSelectedInputMethod();
+ final String defaultImiId = settings.getSelectedInputMethod();
if (!TextUtils.isEmpty(defaultImiId)) {
- if (!mSettings.getMethodMap().containsKey(defaultImiId)) {
+ if (!settings.getMethodMap().containsKey(defaultImiId)) {
Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
if (chooseNewDefaultIMELocked()) {
updateInputMethodsFromSettingsLocked(true);
@@ -5066,26 +5087,26 @@
updateDefaultVoiceImeIfNeededLocked();
// TODO: Instantiate mSwitchingController for each user.
- if (mSettings.getUserId() == mSwitchingController.getUserId()) {
- mSwitchingController.resetCircularListLocked(mSettings.getMethodMap());
+ if (settings.getUserId() == mSwitchingController.getUserId()) {
+ mSwitchingController.resetCircularListLocked(settings.getMethodMap());
} else {
mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
- mContext, mSettings.getMethodMap(), mSettings.getUserId());
+ mContext, settings.getMethodMap(), mCurrentUserId);
}
// TODO: Instantiate mHardwareKeyboardShortcutController for each user.
- if (mSettings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
- mHardwareKeyboardShortcutController.reset(mSettings.getMethodMap());
+ if (settings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
+ mHardwareKeyboardShortcutController.reset(settings.getMethodMap());
} else {
mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
- mSettings.getMethodMap(), mSettings.getUserId());
+ settings.getMethodMap(), settings.getUserId());
}
sendOnNavButtonFlagsChangedLocked();
// Notify InputMethodListListeners of the new installed InputMethods.
- final List<InputMethodInfo> inputMethodList = mSettings.getMethodList();
+ final List<InputMethodInfo> inputMethodList = settings.getMethodList();
mHandler.obtainMessage(MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED,
- mSettings.getUserId(), 0 /* unused */, inputMethodList).sendToTarget();
+ settings.getUserId(), 0 /* unused */, inputMethodList).sendToTarget();
}
@GuardedBy("ImfLock.class")
@@ -5100,11 +5121,12 @@
@GuardedBy("ImfLock.class")
private void updateDefaultVoiceImeIfNeededLocked() {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
final String systemSpeechRecognizer =
mContext.getString(com.android.internal.R.string.config_systemSpeechRecognizer);
- final String currentDefaultVoiceImeId = mSettings.getDefaultVoiceInputMethod();
+ final String currentDefaultVoiceImeId = settings.getDefaultVoiceInputMethod();
final InputMethodInfo newSystemVoiceIme = InputMethodInfoUtils.chooseSystemVoiceIme(
- mSettings.getMethodMap(), systemSpeechRecognizer, currentDefaultVoiceImeId);
+ settings.getMethodMap(), systemSpeechRecognizer, currentDefaultVoiceImeId);
if (newSystemVoiceIme == null) {
if (DEBUG) {
Slog.i(TAG, "Found no valid default Voice IME. If the user is still locked,"
@@ -5113,7 +5135,7 @@
// Clear DEFAULT_VOICE_INPUT_METHOD when necessary. Note that InputMethodSettings
// does not update the actual Secure Settings until the user is unlocked.
if (!TextUtils.isEmpty(currentDefaultVoiceImeId)) {
- mSettings.putDefaultVoiceInputMethod("");
+ settings.putDefaultVoiceInputMethod("");
// We don't support disabling the voice ime when a package is removed from the
// config.
}
@@ -5126,7 +5148,7 @@
Slog.i(TAG, "Enabling the default Voice IME:" + newSystemVoiceIme);
}
setInputMethodEnabledLocked(newSystemVoiceIme.getId(), true);
- mSettings.putDefaultVoiceInputMethod(newSystemVoiceIme.getId());
+ settings.putDefaultVoiceInputMethod(newSystemVoiceIme.getId());
}
// ----------------------------------------------------------------------
@@ -5141,8 +5163,9 @@
*/
@GuardedBy("ImfLock.class")
private boolean setInputMethodEnabledLocked(String id, boolean enabled) {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
if (enabled) {
- final String enabledImeIdsStr = mSettings.getEnabledInputMethodsStr();
+ final String enabledImeIdsStr = settings.getEnabledInputMethodsStr();
final String newEnabledImeIdsStr = InputMethodUtils.concatEnabledImeIds(
enabledImeIdsStr, id);
if (TextUtils.equals(enabledImeIdsStr, newEnabledImeIdsStr)) {
@@ -5150,29 +5173,29 @@
// Nothing to do. The previous state was enabled.
return true;
}
- mSettings.putEnabledInputMethodsStr(newEnabledImeIdsStr);
+ settings.putEnabledInputMethodsStr(newEnabledImeIdsStr);
// Previous state was disabled.
return false;
} else {
- final List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
+ final List<Pair<String, ArrayList<String>>> enabledInputMethodsList = settings
.getEnabledInputMethodsAndSubtypeList();
StringBuilder builder = new StringBuilder();
- if (mSettings.buildAndPutEnabledInputMethodsStrRemovingId(
+ if (settings.buildAndPutEnabledInputMethodsStrRemovingId(
builder, enabledInputMethodsList, id)) {
if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
// Disabled input method is currently selected, switch to another one.
- final String selId = mSettings.getSelectedInputMethod();
+ final String selId = settings.getSelectedInputMethod();
if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
resetSelectedInputMethodAndSubtypeLocked("");
}
- } else if (id.equals(mSettings.getSelectedDefaultDeviceInputMethod())) {
+ } else if (id.equals(settings.getSelectedDefaultDeviceInputMethod())) {
// Disabled default device IME while using a virtual device one, choose a
// new default one but only update the settings.
InputMethodInfo newDefaultIme =
InputMethodInfoUtils.getMostApplicableDefaultIME(
- mSettings.getEnabledInputMethodList());
- mSettings.putSelectedDefaultDeviceInputMethod(
+ settings.getEnabledInputMethodList());
+ settings.putSelectedDefaultDeviceInputMethod(
newDefaultIme == null ? null : newDefaultIme.getId());
}
// Previous state was enabled.
@@ -5188,29 +5211,30 @@
@GuardedBy("ImfLock.class")
private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
boolean setSubtypeOnly) {
- mSettings.saveCurrentInputMethodAndSubtypeToHistory(getSelectedMethodIdLocked(),
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ settings.saveCurrentInputMethodAndSubtypeToHistory(getSelectedMethodIdLocked(),
mCurrentSubtype);
// Set Subtype here
if (imi == null || subtypeId < 0) {
- mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+ settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
mCurrentSubtype = null;
} else {
if (subtypeId < imi.getSubtypeCount()) {
InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
- mSettings.putSelectedSubtype(subtype.hashCode());
+ settings.putSelectedSubtype(subtype.hashCode());
mCurrentSubtype = subtype;
} else {
- mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+ settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
// If the subtype is not specified, choose the most applicable one
mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
}
}
- notifyInputMethodSubtypeChangedLocked(mSettings.getUserId(), imi, mCurrentSubtype);
+ notifyInputMethodSubtypeChangedLocked(settings.getUserId(), imi, mCurrentSubtype);
if (!setSubtypeOnly) {
// Set InputMethod here
- mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
+ settings.putSelectedInputMethod(imi != null ? imi.getId() : "");
}
}
@@ -5218,13 +5242,15 @@
private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
mDeviceIdToShowIme = DEVICE_ID_DEFAULT;
mDisplayIdToShowIme = INVALID_DISPLAY;
- mSettings.putSelectedDefaultDeviceInputMethod(null);
- InputMethodInfo imi = mSettings.getMethodMap().get(newDefaultIme);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ settings.putSelectedDefaultDeviceInputMethod(null);
+
+ InputMethodInfo imi = settings.getMethodMap().get(newDefaultIme);
int lastSubtypeId = NOT_A_SUBTYPE_ID;
// newDefaultIme is empty when there is no candidate for the selected IME.
if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
- String subtypeHashCode = mSettings.getLastSubtypeForInputMethod(newDefaultIme);
+ String subtypeHashCode = settings.getLastSubtypeForInputMethod(newDefaultIme);
if (subtypeHashCode != null) {
try {
lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(imi,
@@ -5251,12 +5277,12 @@
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
synchronized (ImfLock.class) {
- if (mSettings.getUserId() == userId) {
+ if (mCurrentUserId == userId) {
return getCurrentInputMethodSubtypeLocked();
}
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
- return settings.getCurrentInputMethodSubtypeForNonCurrentUsers();
+ return InputMethodSettingsRepository.get(userId)
+ .getCurrentInputMethodSubtypeForNonCurrentUsers();
}
}
@@ -5276,26 +5302,27 @@
if (selectedMethodId == null) {
return null;
}
- final boolean subtypeIsSelected = mSettings.isSubtypeSelected();
- final InputMethodInfo imi = mSettings.getMethodMap().get(selectedMethodId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ final boolean subtypeIsSelected = settings.isSubtypeSelected();
+ final InputMethodInfo imi = settings.getMethodMap().get(selectedMethodId);
if (imi == null || imi.getSubtypeCount() == 0) {
return null;
}
if (!subtypeIsSelected || mCurrentSubtype == null
|| !SubtypeUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
- int subtypeId = mSettings.getSelectedInputMethodSubtypeId(selectedMethodId);
+ int subtypeId = settings.getSelectedInputMethodSubtypeId(selectedMethodId);
if (subtypeId == NOT_A_SUBTYPE_ID) {
// If there are no selected subtypes, the framework will try to find
// the most applicable subtype from explicitly or implicitly enabled
// subtypes.
List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
- mSettings.getEnabledInputMethodSubtypeList(imi, true);
+ settings.getEnabledInputMethodSubtypeList(imi, true);
// If there is only one explicitly or implicitly enabled subtype,
// just returns it.
if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
} else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
- final String locale = SystemLocaleWrapper.get(mSettings.getUserId())
+ final String locale = SystemLocaleWrapper.get(settings.getUserId())
.get(0).toString();
mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtype(
explicitlyOrImplicitlyEnabledSubtypes,
@@ -5318,38 +5345,22 @@
*/
@GuardedBy("ImfLock.class")
private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) {
- final InputMethodSettings settings;
- if (userId == mSettings.getUserId()) {
- settings = mSettings;
- } else {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeMapRepository.get(userId);
- settings = queryInputMethodServicesInternal(mContext, userId,
- additionalSubtypeMap, DirectBootAwareness.AUTO);
- }
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
return settings.getMethodMap().get(settings.getSelectedInputMethod());
}
@GuardedBy("ImfLock.class")
- private InputMethodSettings queryMethodMapForUserLocked(@UserIdInt int userId) {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeMapRepository.get(userId);
- return queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
- DirectBootAwareness.AUTO);
- }
-
- @GuardedBy("ImfLock.class")
private boolean switchToInputMethodLocked(String imeId, @UserIdInt int userId) {
- if (userId == mSettings.getUserId()) {
- if (!mSettings.getMethodMap().containsKey(imeId)
- || !mSettings.getEnabledInputMethodList()
- .contains(mSettings.getMethodMap().get(imeId))) {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+ if (userId == mCurrentUserId) {
+ if (!settings.getMethodMap().containsKey(imeId)
+ || !settings.getEnabledInputMethodList()
+ .contains(settings.getMethodMap().get(imeId))) {
return false; // IME is not found or not enabled.
}
setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
return true;
}
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
if (!settings.getMethodMap().containsKey(imeId)
|| !settings.getEnabledInputMethodList().contains(
settings.getMethodMap().get(imeId))) {
@@ -5390,8 +5401,9 @@
@GuardedBy("ImfLock.class")
private void switchKeyboardLayoutLocked(int direction) {
- final InputMethodInfo currentImi = mSettings.getMethodMap().get(
- getSelectedMethodIdLocked());
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+
+ final InputMethodInfo currentImi = settings.getMethodMap().get(getSelectedMethodIdLocked());
if (currentImi == null) {
return;
}
@@ -5403,7 +5415,7 @@
if (nextSubtypeHandle == null) {
return;
}
- final InputMethodInfo nextImi = mSettings.getMethodMap().get(nextSubtypeHandle.getImeId());
+ final InputMethodInfo nextImi = settings.getMethodMap().get(nextSubtypeHandle.getImeId());
if (nextImi == null) {
return;
}
@@ -5482,17 +5494,14 @@
@Override
public boolean setInputMethodEnabled(String imeId, boolean enabled, @UserIdInt int userId) {
synchronized (ImfLock.class) {
- if (userId == mSettings.getUserId()) {
- if (!mSettings.getMethodMap().containsKey(imeId)) {
- return false; // IME is not found.
- }
- setInputMethodEnabledLocked(imeId, enabled);
- return true;
- }
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (!settings.getMethodMap().containsKey(imeId)) {
return false; // IME is not found.
}
+ if (userId == mCurrentUserId) {
+ setInputMethodEnabledLocked(imeId, enabled);
+ return true;
+ }
if (enabled) {
final String enabledImeIdsStr = settings.getEnabledInputMethodsStr();
final String newEnabledImeIdsStr = InputMethodUtils.concatEnabledImeIds(
@@ -5548,9 +5557,11 @@
public void reportImeControl(@Nullable IBinder windowToken) {
synchronized (ImfLock.class) {
if (mImeBindingState.mFocusedWindow != windowToken) {
- // mCurPerceptible was set by the focused window, but it is no longer in
- // control, so we reset mCurPerceptible.
- mCurPerceptible = true;
+ // A perceptible value was set for the focused window, but it is no longer in
+ // control, so we reset the perceptible for the window passed as argument.
+ // TODO(b/314149476): Investigate whether this logic is still relevant, if not
+ // then consider removing using concurrent_input_methods feature flag.
+ mFocusedWindowPerceptible.put(windowToken, true);
}
}
}
@@ -5825,8 +5836,9 @@
final Printer p = new PrintWriterPrinter(pw);
synchronized (ImfLock.class) {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
p.println("Current Input Method Manager state:");
- final List<InputMethodInfo> methodList = mSettings.getMethodList();
+ final List<InputMethodInfo> methodList = settings.getMethodList();
int numImes = methodList.size();
p.println(" Input Methods: mMethodMapUpdateCount=" + mMethodMapUpdateCount);
for (int i = 0; i < numImes; i++) {
@@ -5850,11 +5862,11 @@
p.println(" curSession=" + c.mCurSession);
};
mClientController.forAllClients(clientControllerDump);
-
+ p.println(" mCurrentUserId=" + mCurrentUserId);
p.println(" mCurMethodId=" + getSelectedMethodIdLocked());
client = mCurClient;
p.println(" mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked());
- p.println(" mCurPerceptible=" + mCurPerceptible);
+ p.println(" mFocusedWindowPerceptible=" + mFocusedWindowPerceptible);
mImeBindingState.dump(" ", p);
p.println(" mCurId=" + getCurIdLocked() + " mHaveConnection=" + hasConnectionLocked()
+ " mBoundToMethod=" + mBoundToMethod + " mVisibleBound="
@@ -5876,8 +5888,6 @@
? Arrays.toString(mStylusIds.toArray()) : ""));
p.println(" mSwitchingController:");
mSwitchingController.dump(p);
- p.println(" mSettings:");
- mSettings.dump(p, " ");
p.println(" mStartInputHistory:");
mStartInputHistory.dump(pw, " ");
@@ -6132,7 +6142,7 @@
}
synchronized (ImfLock.class) {
final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
- mSettings.getUserId(), shellCommand.getErrPrintWriter());
+ mCurrentUserId, shellCommand.getErrPrintWriter());
try (PrintWriter pr = shellCommand.getOutPrintWriter()) {
for (int userId : userIds) {
final List<InputMethodInfo> methods = all
@@ -6177,7 +6187,7 @@
PrintWriter error = shellCommand.getErrPrintWriter()) {
synchronized (ImfLock.class) {
final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
- mSettings.getUserId(), shellCommand.getErrPrintWriter());
+ mCurrentUserId, shellCommand.getErrPrintWriter());
for (int userId : userIds) {
if (!userHasDebugPriv(userId, shellCommand)) {
continue;
@@ -6236,14 +6246,14 @@
PrintWriter error) {
boolean failedToEnableUnknownIme = false;
boolean previouslyEnabled = false;
- if (userId == mSettings.getUserId()) {
- if (enabled && !mSettings.getMethodMap().containsKey(imeId)) {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+ if (userId == mCurrentUserId) {
+ if (enabled && !settings.getMethodMap().containsKey(imeId)) {
failedToEnableUnknownIme = true;
} else {
previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled);
}
} else {
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
if (enabled) {
if (!settings.getMethodMap().containsKey(imeId)) {
failedToEnableUnknownIme = true;
@@ -6298,7 +6308,7 @@
PrintWriter error = shellCommand.getErrPrintWriter()) {
synchronized (ImfLock.class) {
final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
- mSettings.getUserId(), shellCommand.getErrPrintWriter());
+ mCurrentUserId, shellCommand.getErrPrintWriter());
for (int userId : userIds) {
if (!userHasDebugPriv(userId, shellCommand)) {
continue;
@@ -6338,7 +6348,7 @@
synchronized (ImfLock.class) {
try (PrintWriter out = shellCommand.getOutPrintWriter()) {
final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
- mSettings.getUserId(), shellCommand.getErrPrintWriter());
+ mCurrentUserId, shellCommand.getErrPrintWriter());
for (int userId : userIds) {
if (!userHasDebugPriv(userId, shellCommand)) {
continue;
@@ -6350,15 +6360,16 @@
}
final String nextIme;
final List<InputMethodInfo> nextEnabledImes;
- if (userId == mSettings.getUserId()) {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+ if (userId == mCurrentUserId) {
hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
mBindingController.unbindCurrentMethod();
// Enable default IMEs, disable others
- var toDisable = mSettings.getEnabledInputMethodList();
+ var toDisable = settings.getEnabledInputMethodList();
var defaultEnabled = InputMethodInfoUtils.getDefaultEnabledImes(
- mContext, mSettings.getMethodList());
+ mContext, settings.getMethodList());
toDisable.removeAll(defaultEnabled);
for (InputMethodInfo info : toDisable) {
setInputMethodEnabledLocked(info.getId(), false);
@@ -6372,16 +6383,11 @@
}
updateInputMethodsFromSettingsLocked(true /* enabledMayChange */);
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
- getPackageManagerForUser(mContext, mSettings.getUserId()),
- mSettings.getEnabledInputMethodList());
- nextIme = mSettings.getSelectedInputMethod();
- nextEnabledImes = mSettings.getEnabledInputMethodList();
+ getPackageManagerForUser(mContext, settings.getUserId()),
+ settings.getEnabledInputMethodList());
+ nextIme = settings.getSelectedInputMethod();
+ nextEnabledImes = settings.getEnabledInputMethodList();
} else {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeMapRepository.get(userId);
- final InputMethodSettings settings = queryInputMethodServicesInternal(
- mContext, userId, additionalSubtypeMap, DirectBootAwareness.AUTO);
-
nextEnabledImes = InputMethodInfoUtils.getDefaultEnabledImes(mContext,
settings.getMethodList());
nextIme = InputMethodInfoUtils.getMostApplicableDefaultIME(
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
new file mode 100644
index 0000000..60b9a4c
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+final class InputMethodSettingsRepository {
+ @GuardedBy("ImfLock.class")
+ @NonNull
+ private static final SparseArray<InputMethodSettings> sPerUserMap = new SparseArray<>();
+
+ /**
+ * Not intended to be instantiated.
+ */
+ private InputMethodSettingsRepository() {
+ }
+
+ @NonNull
+ @GuardedBy("ImfLock.class")
+ static InputMethodSettings get(@UserIdInt int userId) {
+ final InputMethodSettings obj = sPerUserMap.get(userId);
+ if (obj != null) {
+ return obj;
+ }
+ return InputMethodSettings.createEmptyMap(userId);
+ }
+
+ @GuardedBy("ImfLock.class")
+ static void put(@UserIdInt int userId, @NonNull InputMethodSettings obj) {
+ sPerUserMap.put(userId, obj);
+ }
+
+ static void initialize(@NonNull Handler handler, @NonNull Context context) {
+ final UserManagerInternal userManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+ handler.post(() -> {
+ userManagerInternal.addUserLifecycleListener(
+ new UserManagerInternal.UserLifecycleListener() {
+ @Override
+ public void onUserRemoved(UserInfo user) {
+ final int userId = user.id;
+ handler.post(() -> {
+ synchronized (ImfLock.class) {
+ sPerUserMap.remove(userId);
+ }
+ });
+ }
+ });
+ synchronized (ImfLock.class) {
+ for (int userId : userManagerInternal.getUserIds()) {
+ final InputMethodSettings settings =
+ InputMethodManagerService.queryInputMethodServicesInternal(
+ context,
+ userId,
+ AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
+ sPerUserMap.put(userId, settings);
+ }
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/Sequence.java b/services/core/java/com/android/server/inputmethod/Sequence.java
new file mode 100644
index 0000000..05e31ce
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/Sequence.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * A sequence number utility class that only generate positive numbers.
+ */
+final class Sequence {
+
+ private final Object mLock = new Object();
+
+ private int mSequence;
+
+ int getSequenceNumber() {
+ synchronized (mLock) {
+ return mSequence;
+ }
+ }
+
+ @GuardedBy("ImfLock.class")
+ void advanceSequenceNumber() {
+ synchronized (mLock) {
+ mSequence++;
+ if (mSequence <= 0) {
+ mSequence = 1;
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/UserData.java b/services/core/java/com/android/server/inputmethod/UserData.java
new file mode 100644
index 0000000..fc2a422
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/UserData.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+final class UserData {
+
+ @NonNull
+ private static final SparseArray<UserData> sPerUserMonitor = new SparseArray<>();
+
+ @UserIdInt
+ final int mUserId;
+
+ @GuardedBy("ImfLock.class")
+ final Sequence mSequence = new Sequence();
+
+ /**
+ * Not intended to be instantiated.
+ */
+ private UserData(int userId) {
+ mUserId = userId;
+ }
+
+ @GuardedBy("ImfLock.class")
+ static UserData getOrCreate(@UserIdInt int userId) {
+ UserData monitor = sPerUserMonitor.get(userId);
+ if (monitor == null) {
+ monitor = new UserData(userId);
+ sPerUserMonitor.put(userId, monitor);
+ }
+ return monitor;
+ }
+
+ static void initialize(Handler handler) {
+ final UserManagerInternal userManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+ userManagerInternal.addUserLifecycleListener(
+ new UserManagerInternal.UserLifecycleListener() {
+ @Override
+ public void onUserRemoved(UserInfo user) {
+ final int userId = user.id;
+ handler.post(() -> {
+ synchronized (ImfLock.class) {
+ sPerUserMonitor.remove(userId);
+ }
+ });
+ }
+
+ @Override
+ public void onUserCreated(UserInfo user, Object unusedToken) {
+ final int userId = user.id;
+ handler.post(() -> {
+ synchronized (ImfLock.class) {
+ getOrCreate(userId);
+ }
+ });
+ }
+ });
+ synchronized (ImfLock.class) {
+ for (int userId : userManagerInternal.getUserIds()) {
+ getOrCreate(userId);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 97ce77c..18b495b 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -340,8 +340,6 @@
static final String TAG = NetworkPolicyLogger.TAG;
private static final boolean LOGD = NetworkPolicyLogger.LOGD;
private static final boolean LOGV = NetworkPolicyLogger.LOGV;
- // TODO: b/304347838 - Remove once the feature is in staging.
- private static final boolean ALWAYS_RESTRICT_BACKGROUND_NETWORK = false;
/**
* No opportunistic quota could be calculated from user data plan or data settings.
@@ -1070,8 +1068,7 @@
}
// The flag is boot-stable.
- mBackgroundNetworkRestricted = ALWAYS_RESTRICT_BACKGROUND_NETWORK
- && Flags.networkBlockedForTopSleepingAndAbove();
+ mBackgroundNetworkRestricted = Flags.networkBlockedForTopSleepingAndAbove();
if (mBackgroundNetworkRestricted) {
// Firewall rules and UidBlockedState will get updated in
// updateRulesForGlobalChangeAL below.
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c1a4571e..e80c79a8 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -741,7 +741,7 @@
private static final int MY_UID = Process.myUid();
private static final int MY_PID = Process.myPid();
- static final IBinder ALLOWLIST_TOKEN = new Binder();
+ private static final IBinder ALLOWLIST_TOKEN = new Binder();
protected RankingHandler mRankingHandler;
private long mLastOverRateLogTime;
private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
@@ -4875,7 +4875,7 @@
// Remove background token before returning notification to untrusted app, this
// ensures the app isn't able to perform background operations that are
// associated with notification interactions.
- notification.overrideAllowlistToken(null);
+ notification.clearAllowlistToken();
return new StatusBarNotification(
sbn.getPackageName(),
sbn.getOpPkg(),
@@ -7884,8 +7884,6 @@
}
}
- notification.overrideAllowlistToken(ALLOWLIST_TOKEN);
-
// Remote views? Are they too big?
checkRemoteViews(pkg, tag, id, notification);
}
diff --git a/core/java/android/app/ondeviceintelligence/Content.aidl b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
similarity index 72%
copy from core/java/android/app/ondeviceintelligence/Content.aidl
copy to services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
index 40f0ef9..81f11b5 100644
--- a/core/java/android/app/ondeviceintelligence/Content.aidl
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
@@ -1,5 +1,5 @@
-/**
- * Copyright (c) 2024, The Android Open Source Project
+/*
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package android.app.ondeviceintelligence;
+package com.android.server.ondeviceintelligence;
-/**
- * @hide
- */
-parcelable Content;
+public interface OnDeviceIntelligenceManagerInternal {
+ String getRemoteServicePackageName();
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
index 71800ef..28682e3 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -16,12 +16,11 @@
package com.android.server.ondeviceintelligence;
-import static android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException.PROCESSING_UPDATE_STATUS_CONNECTION_FAILED;
-
import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.app.AppGlobals;
-import android.app.ondeviceintelligence.Content;
import android.app.ondeviceintelligence.DownloadCallback;
import android.app.ondeviceintelligence.Feature;
import android.app.ondeviceintelligence.IDownloadCallback;
@@ -33,25 +32,32 @@
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.IStreamingResponseCallback;
import android.app.ondeviceintelligence.ITokenInfoCallback;
-import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.os.Binder;
+import android.content.res.Resources;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
import android.os.ICancellationSignal;
+import android.os.Looper;
+import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.service.ondeviceintelligence.IOnDeviceIntelligenceService;
import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
+import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
import android.service.ondeviceintelligence.IRemoteProcessingService;
import android.service.ondeviceintelligence.IRemoteStorageService;
-import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
import android.service.ondeviceintelligence.OnDeviceIntelligenceService;
import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
import android.text.TextUtils;
@@ -62,8 +68,10 @@
import com.android.internal.infra.AndroidFuture;
import com.android.internal.infra.ServiceConnector;
import com.android.internal.os.BackgroundThread;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
+import java.io.FileDescriptor;
import java.util.Objects;
import java.util.Set;
@@ -84,6 +92,9 @@
private static final String TAG = OnDeviceIntelligenceManagerService.class.getSimpleName();
private static final String KEY_SERVICE_ENABLED = "service_enabled";
+ /** Handler message to {@link #resetTemporaryServices()} */
+ private static final int MSG_RESET_TEMPORARY_SERVICE = 0;
+
/** Default value in absence of {@link DeviceConfig} override. */
private static final boolean DEFAULT_SERVICE_ENABLED = true;
private static final String NAMESPACE_ON_DEVICE_INTELLIGENCE = "ondeviceintelligence";
@@ -96,19 +107,30 @@
private RemoteOnDeviceIntelligenceService mRemoteOnDeviceIntelligenceService;
volatile boolean mIsServiceEnabled;
+ @GuardedBy("mLock")
+ private String[] mTemporaryServiceNames;
+
+ /**
+ * Handler used to reset the temporary service names.
+ */
+ @GuardedBy("mLock")
+ private Handler mTemporaryHandler;
+
public OnDeviceIntelligenceManagerService(Context context) {
super(context);
mContext = context;
+ mTemporaryServiceNames = new String[0];
}
@Override
public void onStart() {
publishBinderService(
- Context.ON_DEVICE_INTELLIGENCE_SERVICE, new OnDeviceIntelligenceManagerInternal(),
+ Context.ON_DEVICE_INTELLIGENCE_SERVICE, getOnDeviceIntelligenceManagerService(),
/* allowIsolated = */true);
+ LocalServices.addService(OnDeviceIntelligenceManagerInternal.class,
+ OnDeviceIntelligenceManagerService.this::getRemoteConfiguredPackageName);
}
-
@Override
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
@@ -133,195 +155,211 @@
KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
}
- private final class OnDeviceIntelligenceManagerInternal extends
- IOnDeviceIntelligenceManager.Stub {
- @Override
- public void getVersion(RemoteCallback remoteCallback) throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getVersion");
- Objects.requireNonNull(remoteCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- remoteCallback.sendResult(null);
- return;
+ private IBinder getOnDeviceIntelligenceManagerService() {
+ return new IOnDeviceIntelligenceManager.Stub() {
+ @Override
+ public String getRemoteServicePackageName() {
+ return OnDeviceIntelligenceManagerService.this.getRemoteConfiguredPackageName();
}
- ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
- service -> service.getVersion(remoteCallback));
- }
- @Override
- public void getFeature(int id, IFeatureCallback featureCallback) throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
- Objects.requireNonNull(featureCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- featureCallback.onFailure(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
- return;
+ @Override
+ public void getVersion(RemoteCallback remoteCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getVersion");
+ Objects.requireNonNull(remoteCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ remoteCallback.sendResult(null);
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.run(
+ service -> service.getVersion(remoteCallback));
}
- ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
- service -> service.getFeature(Binder.getCallingUid(), id, featureCallback));
- }
- @Override
- public void listFeatures(IListFeaturesCallback listFeaturesCallback)
- throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
- Objects.requireNonNull(listFeaturesCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- listFeaturesCallback.onFailure(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
- return;
+ @Override
+ public void getFeature(int id, IFeatureCallback featureCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
+ Objects.requireNonNull(featureCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ featureCallback.onFailure(
+ OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.run(
+ service -> service.getFeature(Binder.getCallingUid(), id, featureCallback));
}
- ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
- service -> service.listFeatures(Binder.getCallingUid(), listFeaturesCallback));
- }
- @Override
- public void getFeatureDetails(Feature feature,
- IFeatureDetailsCallback featureDetailsCallback)
- throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatureStatus");
- Objects.requireNonNull(feature);
- Objects.requireNonNull(featureDetailsCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- featureDetailsCallback.onFailure(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
- return;
+ @Override
+ public void listFeatures(IListFeaturesCallback listFeaturesCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
+ Objects.requireNonNull(listFeaturesCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ listFeaturesCallback.onFailure(
+ OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.run(
+ service -> service.listFeatures(Binder.getCallingUid(),
+ listFeaturesCallback));
}
- ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
- service -> service.getFeatureDetails(Binder.getCallingUid(), feature,
- featureDetailsCallback));
- }
- @Override
- public void requestFeatureDownload(Feature feature, ICancellationSignal cancellationSignal,
- IDownloadCallback downloadCallback) throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal requestFeatureDownload");
- Objects.requireNonNull(feature);
- Objects.requireNonNull(downloadCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- downloadCallback.onDownloadFailed(
- DownloadCallback.DOWNLOAD_FAILURE_STATUS_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
+ @Override
+ public void getFeatureDetails(Feature feature,
+ IFeatureDetailsCallback featureDetailsCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatureStatus");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(featureDetailsCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ featureDetailsCallback.onFailure(
+ OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.run(
+ service -> service.getFeatureDetails(Binder.getCallingUid(), feature,
+ featureDetailsCallback));
}
- ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
- service -> service.requestFeatureDownload(Binder.getCallingUid(), feature,
- cancellationSignal,
- downloadCallback));
- }
-
- @Override
- public void requestTokenInfo(Feature feature,
- Content request, ICancellationSignal cancellationSignal,
- ITokenInfoCallback tokenInfoCallback) throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal prepareFeatureProcessing");
- Objects.requireNonNull(feature);
- Objects.requireNonNull(request);
- Objects.requireNonNull(tokenInfoCallback);
-
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- tokenInfoCallback.onFailure(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
+ @Override
+ public void requestFeatureDownload(Feature feature,
+ ICancellationSignal cancellationSignal,
+ IDownloadCallback downloadCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal requestFeatureDownload");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(downloadCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ downloadCallback.onDownloadFailed(
+ DownloadCallback.DOWNLOAD_FAILURE_STATUS_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.run(
+ service -> service.requestFeatureDownload(Binder.getCallingUid(), feature,
+ cancellationSignal,
+ downloadCallback));
}
- ensureRemoteInferenceServiceInitialized();
- mRemoteInferenceService.post(
- service -> service.requestTokenInfo(Binder.getCallingUid(), feature, request,
- cancellationSignal,
- tokenInfoCallback));
- }
- @Override
- public void processRequest(Feature feature,
- Content request,
- int requestType,
- ICancellationSignal cancellationSignal,
- IProcessingSignal processingSignal,
- IResponseCallback responseCallback)
- throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequest");
- Objects.requireNonNull(feature);
- Objects.requireNonNull(responseCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- responseCallback.onFailure(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
- }
- ensureRemoteInferenceServiceInitialized();
- mRemoteInferenceService.post(
- service -> service.processRequest(Binder.getCallingUid(), feature, request,
- requestType,
- cancellationSignal, processingSignal,
- responseCallback));
- }
- @Override
- public void processRequestStreaming(Feature feature,
- Content request,
- int requestType,
- ICancellationSignal cancellationSignal,
- IProcessingSignal processingSignal,
- IStreamingResponseCallback streamingCallback) throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequestStreaming");
- Objects.requireNonNull(feature);
- Objects.requireNonNull(streamingCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- streamingCallback.onFailure(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
+ @Override
+ public void requestTokenInfo(Feature feature,
+ Bundle request, ICancellationSignal cancellationSignal,
+ ITokenInfoCallback tokenInfoCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal prepareFeatureProcessing");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(tokenInfoCallback);
+
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ tokenInfoCallback.onFailure(
+ OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ }
+ ensureRemoteInferenceServiceInitialized();
+ mRemoteInferenceService.run(
+ service -> service.requestTokenInfo(Binder.getCallingUid(), feature,
+ request,
+ cancellationSignal,
+ tokenInfoCallback));
}
- ensureRemoteInferenceServiceInitialized();
- mRemoteInferenceService.post(
- service -> service.processRequestStreaming(Binder.getCallingUid(), feature,
- request, requestType,
- cancellationSignal, processingSignal,
- streamingCallback));
- }
+
+ @Override
+ public void processRequest(Feature feature,
+ Bundle request,
+ int requestType,
+ ICancellationSignal cancellationSignal,
+ IProcessingSignal processingSignal,
+ IResponseCallback responseCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequest");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(responseCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ responseCallback.onFailure(
+ OnDeviceIntelligenceException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ }
+ ensureRemoteInferenceServiceInitialized();
+ mRemoteInferenceService.run(
+ service -> service.processRequest(Binder.getCallingUid(), feature, request,
+ requestType,
+ cancellationSignal, processingSignal,
+ responseCallback));
+ }
+
+ @Override
+ public void processRequestStreaming(Feature feature,
+ Bundle request,
+ int requestType,
+ ICancellationSignal cancellationSignal,
+ IProcessingSignal processingSignal,
+ IStreamingResponseCallback streamingCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequestStreaming");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(streamingCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ streamingCallback.onFailure(
+ OnDeviceIntelligenceException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ }
+ ensureRemoteInferenceServiceInitialized();
+ mRemoteInferenceService.run(
+ service -> service.processRequestStreaming(Binder.getCallingUid(), feature,
+ request, requestType,
+ cancellationSignal, processingSignal,
+ streamingCallback));
+ }
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+ new OnDeviceIntelligenceShellCommand(OnDeviceIntelligenceManagerService.this).exec(
+ this, in, out, err, args, callback, resultReceiver);
+ }
+ };
}
private void ensureRemoteIntelligenceServiceInitialized() throws RemoteException {
synchronized (mLock) {
if (mRemoteOnDeviceIntelligenceService == null) {
- String serviceName = mContext.getResources().getString(
- R.string.config_defaultOnDeviceIntelligenceService);
+ String serviceName = getServiceNames()[0];
validateService(serviceName, false);
mRemoteOnDeviceIntelligenceService = new RemoteOnDeviceIntelligenceService(mContext,
ComponentName.unflattenFromString(serviceName),
@@ -352,13 +390,13 @@
IProcessingUpdateStatusCallback callback) {
try {
ensureRemoteInferenceServiceInitialized();
- mRemoteInferenceService.post(
+ mRemoteInferenceService.run(
service -> service.updateProcessingState(
processingState, callback));
} catch (RemoteException unused) {
try {
callback.onFailure(
- PROCESSING_UPDATE_STATUS_CONNECTION_FAILED,
+ OnDeviceIntelligenceException.PROCESSING_UPDATE_STATUS_CONNECTION_FAILED,
"Received failure invoking the remote processing service.");
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to send failure status.", ex);
@@ -371,8 +409,7 @@
private void ensureRemoteInferenceServiceInitialized() throws RemoteException {
synchronized (mLock) {
if (mRemoteInferenceService == null) {
- String serviceName = mContext.getResources().getString(
- R.string.config_defaultOnDeviceSandboxedInferenceService);
+ String serviceName = getServiceNames()[1];
validateService(serviceName, true);
mRemoteInferenceService = new RemoteOnDeviceSandboxedInferenceService(mContext,
ComponentName.unflattenFromString(serviceName),
@@ -384,7 +421,7 @@
@NonNull IOnDeviceSandboxedInferenceService service) {
try {
ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
+ mRemoteOnDeviceIntelligenceService.run(
intelligenceService -> intelligenceService.notifyInferenceServiceConnected());
service.registerRemoteStorageService(
getIRemoteStorageService());
@@ -404,7 +441,7 @@
public void getReadOnlyFileDescriptor(
String filePath,
AndroidFuture<ParcelFileDescriptor> future) {
- mRemoteOnDeviceIntelligenceService.post(
+ mRemoteOnDeviceIntelligenceService.run(
service -> service.getReadOnlyFileDescriptor(
filePath, future));
}
@@ -413,7 +450,7 @@
public void getReadOnlyFeatureFileDescriptorMap(
Feature feature,
RemoteCallback remoteCallback) {
- mRemoteOnDeviceIntelligenceService.post(
+ mRemoteOnDeviceIntelligenceService.run(
service -> service.getReadOnlyFeatureFileDescriptorMap(
feature, remoteCallback));
}
@@ -469,4 +506,92 @@
return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
&& (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
}
+
+ @Nullable
+ public String getRemoteConfiguredPackageName() {
+ try {
+ String[] serviceNames = getServiceNames();
+ ComponentName componentName = ComponentName.unflattenFromString(serviceNames[1]);
+ if (componentName != null) {
+ return componentName.getPackageName();
+ }
+ } catch (Resources.NotFoundException e) {
+ Slog.e(TAG, "Could not find resource", e);
+ }
+
+ return null;
+ }
+
+
+ protected String[] getServiceNames() throws Resources.NotFoundException {
+ // TODO 329240495 : Consider a small class with explicit field names for the two services
+ synchronized (mLock) {
+ if (mTemporaryServiceNames != null && mTemporaryServiceNames.length == 2) {
+ return mTemporaryServiceNames;
+ }
+ }
+ return new String[]{mContext.getResources().getString(
+ R.string.config_defaultOnDeviceIntelligenceService),
+ mContext.getResources().getString(
+ R.string.config_defaultOnDeviceSandboxedInferenceService)};
+ }
+
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void setTemporaryServices(@NonNull String[] componentNames, int durationMs) {
+ Objects.requireNonNull(componentNames);
+ enforceShellOnly(Binder.getCallingUid(), "setTemporaryServices");
+ mContext.enforceCallingPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ synchronized (mLock) {
+ mTemporaryServiceNames = componentNames;
+
+ if (mTemporaryHandler == null) {
+ mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
+ synchronized (mLock) {
+ resetTemporaryServices();
+ }
+ } else {
+ Slog.wtf(TAG, "invalid handler msg: " + msg);
+ }
+ }
+ };
+ } else {
+ mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
+ }
+
+ if (durationMs != -1) {
+ mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs);
+ }
+ }
+ }
+
+ public void resetTemporaryServices() {
+ synchronized (mLock) {
+ if (mTemporaryHandler != null) {
+ mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
+ mTemporaryHandler = null;
+ }
+
+ mRemoteInferenceService = null;
+ mRemoteOnDeviceIntelligenceService = null;
+ mTemporaryServiceNames = new String[0];
+ }
+ }
+
+ /**
+ * Throws if the caller is not of a shell (or root) UID.
+ *
+ * @param callingUid pass Binder.callingUid().
+ */
+ public static void enforceShellOnly(int callingUid, String message) {
+ if (callingUid == android.os.Process.SHELL_UID
+ || callingUid == android.os.Process.ROOT_UID) {
+ return; // okay
+ }
+
+ throw new SecurityException(message + ": Only shell user can call it");
+ }
}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
new file mode 100644
index 0000000..a76d8a3
--- /dev/null
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.ondeviceintelligence;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+final class OnDeviceIntelligenceShellCommand extends ShellCommand {
+ private static final String TAG = OnDeviceIntelligenceShellCommand.class.getSimpleName();
+
+ @NonNull
+ private final OnDeviceIntelligenceManagerService mService;
+
+ OnDeviceIntelligenceShellCommand(@NonNull OnDeviceIntelligenceManagerService service) {
+ mService = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+
+ switch (cmd) {
+ case "set-temporary-services":
+ return setTemporaryServices();
+ case "get-services":
+ return getConfiguredServices();
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("OnDeviceIntelligenceShellCommand commands: ");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println();
+ pw.println(
+ " set-temporary-services [IntelligenceServiceComponentName] "
+ + "[InferenceServiceComponentName] [DURATION]");
+ pw.println(" Temporarily (for DURATION ms) changes the service implementations.");
+ pw.println(" To reset, call without any arguments.");
+
+ pw.println(" get-services To get the names of services that are currently being used.");
+ }
+
+ private int setTemporaryServices() {
+ final PrintWriter out = getOutPrintWriter();
+ final String intelligenceServiceName = getNextArg();
+ final String inferenceServiceName = getNextArg();
+ if (getRemainingArgsCount() == 0 && intelligenceServiceName == null
+ && inferenceServiceName == null) {
+ mService.resetTemporaryServices();
+ out.println("OnDeviceIntelligenceManagerService temporary reset. ");
+ return 0;
+ }
+
+ Objects.requireNonNull(intelligenceServiceName);
+ Objects.requireNonNull(inferenceServiceName);
+ final int duration = Integer.parseInt(getNextArgRequired());
+ mService.setTemporaryServices(
+ new String[]{intelligenceServiceName, inferenceServiceName}, duration);
+ out.println("OnDeviceIntelligenceService temporarily set to " + intelligenceServiceName
+ + " \n and \n OnDeviceTrustedInferenceService set to " + inferenceServiceName
+ + " for " + duration + "ms");
+ return 0;
+ }
+
+ private int getConfiguredServices() {
+ final PrintWriter out = getOutPrintWriter();
+ String[] services = mService.getServiceNames();
+ out.println("OnDeviceIntelligenceService set to : " + services[0]
+ + " \n and \n OnDeviceTrustedInferenceService set to : " + services[1]);
+ return 0;
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 4bfd077..4bec61a 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2892,17 +2892,20 @@
mPm.notifyPackageChanged(packageName, request.getAppId());
}
- // Apply restricted settings on potentially dangerous packages. Needs to happen
- // after appOpsManager is notified of the new package
- if (request.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
- || request.getPackageSource()
- == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) {
- final int appId = request.getAppId();
- mPm.mHandler.post(() -> {
- for (int userId : firstUserIds) {
- enableRestrictedSettings(packageName, appId, userId);
- }
- });
+ if (!android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
+ || !android.security.Flags.extendEcmToAllSettings()) {
+ // Apply restricted settings on potentially dangerous packages. Needs to happen
+ // after appOpsManager is notified of the new package
+ if (request.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
+ || request.getPackageSource()
+ == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) {
+ final int appId = request.getAppId();
+ mPm.mHandler.post(() -> {
+ for (int userId : firstUserIds) {
+ enableRestrictedSettings(packageName, appId, userId);
+ }
+ });
+ }
}
// Log current value of "unknown sources" setting
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index ba90378..3eeeae7 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -148,6 +148,7 @@
import android.os.storage.StorageManager;
import android.provider.DeviceConfig;
import android.provider.Settings.Global;
+import android.service.persistentdata.PersistentDataBlockManager;
import android.stats.devicepolicy.DevicePolicyEnums;
import android.system.ErrnoException;
import android.system.Int64Ref;
@@ -964,13 +965,22 @@
private boolean isEmergencyInstallerEnabled(String packageName, Computer snapshot) {
final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName);
- if (ps == null || ps.getPkg() == null) {
+ if (ps == null || ps.getPkg() == null || !ps.isSystem()) {
return false;
}
+ int uid = UserHandle.getUid(userId, ps.getAppId());
String emergencyInstaller = ps.getPkg().getEmergencyInstaller();
if (emergencyInstaller == null || !ArrayUtils.contains(
- snapshot.getPackagesForUid(mInstallerUid),
- emergencyInstaller)) {
+ snapshot.getPackagesForUid(mInstallerUid), emergencyInstaller)) {
+ return false;
+ }
+ // Only system installers can have an emergency installer
+ if (PackageManager.PERMISSION_GRANTED
+ != snapshot.checkUidPermission(Manifest.permission.INSTALL_PACKAGES, uid)
+ && PackageManager.PERMISSION_GRANTED
+ != snapshot.checkUidPermission(Manifest.permission.INSTALL_PACKAGE_UPDATES, uid)
+ && PackageManager.PERMISSION_GRANTED
+ != snapshot.checkUidPermission(Manifest.permission.INSTALL_SELF_UPDATES, uid)) {
return false;
}
return (snapshot.checkUidPermission(Manifest.permission.EMERGENCY_INSTALL_PACKAGES,
@@ -2355,8 +2365,21 @@
assertPreparedAndNotDestroyedLocked("commit of session " + sessionId);
assertNoWriteFileTransfersOpenLocked();
- final boolean isSecureFrpEnabled =
- Global.getInt(mContext.getContentResolver(), Global.SECURE_FRP_MODE, 0) == 1;
+ boolean isSecureFrpEnabled;
+ if (android.security.Flags.frpEnforcement()) {
+ PersistentDataBlockManager pdbManager =
+ mContext.getSystemService(PersistentDataBlockManager.class);
+ if (pdbManager == null) {
+ // Some devices may not support FRP. In that case, we can't block the install
+ // accordingly.
+ isSecureFrpEnabled = false;
+ } else {
+ isSecureFrpEnabled = pdbManager.isFactoryResetProtectionActive();
+ }
+ } else {
+ isSecureFrpEnabled = Global.getInt(mContext.getContentResolver(),
+ Global.SECURE_FRP_MODE, 0) == 1;
+ }
if (isSecureFrpEnabled
&& !isSecureFrpInstallAllowed(mContext, Binder.getCallingUid())) {
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index c9fd261..1919137 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -217,8 +217,7 @@
private static final Set<String> IMMUTABLE_BY_OWNERS = Sets.newArraySet(
UserManager.DISALLOW_RECORD_AUDIO,
UserManager.DISALLOW_WALLPAPER,
- UserManager.DISALLOW_OEM_UNLOCK,
- UserManager.DISALLOW_ADD_PRIVATE_PROFILE
+ UserManager.DISALLOW_OEM_UNLOCK
);
/**
@@ -293,7 +292,8 @@
UserManager.DISALLOW_USB_FILE_TRANSFER,
UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
UserManager.DISALLOW_UNMUTE_MICROPHONE,
- UserManager.DISALLOW_CONFIG_DEFAULT_APPS
+ UserManager.DISALLOW_CONFIG_DEFAULT_APPS,
+ UserManager.DISALLOW_ADD_PRIVATE_PROFILE
);
/**
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 266418f..9e31748 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1506,9 +1506,7 @@
}
private void stemPrimaryPress(int count) {
- if (DEBUG_INPUT) {
- Slog.d(TAG, "stemPrimaryPress: " + count);
- }
+ Slog.d(TAG, "stemPrimaryPress: " + count);
if (count == 3) {
stemPrimaryTriplePressAction(mTriplePressOnStemPrimaryBehavior);
} else if (count == 2) {
@@ -1519,22 +1517,18 @@
}
private void stemPrimarySinglePressAction(int behavior) {
- if (DEBUG_INPUT) {
- Slog.d(TAG, "stemPrimarySinglePressAction: behavior=" + behavior);
- }
+ Slog.d(TAG, "stemPrimarySinglePressAction: behavior=" + behavior);
if (behavior == SHORT_PRESS_PRIMARY_NOTHING) return;
final boolean keyguardActive = mKeyguardDelegate != null && mKeyguardDelegate.isShowing();
if (keyguardActive) {
// If keyguarded then notify the keyguard.
mKeyguardDelegate.onSystemKeyPressed(KeyEvent.KEYCODE_STEM_PRIMARY);
+ Slog.d(TAG, "stemPrimarySinglePressAction: skip due to keyguard");
return;
}
switch (behavior) {
case SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS:
- if (DEBUG_INPUT) {
- Slog.d(TAG, "Executing stem primary short press action behavior.");
- }
Intent allAppsIntent = new Intent(Intent.ACTION_ALL_APPS);
allAppsIntent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK
@@ -1542,12 +1536,6 @@
startActivityAsUser(allAppsIntent, UserHandle.CURRENT_OR_SELF);
break;
case SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY:
- if (DEBUG_INPUT) {
- Slog.d(
- TAG,
- "Executing stem primary short press action behavior for launching "
- + "target activity.");
- }
if (mPrimaryShortPressTargetActivity != null) {
Intent targetActivityIntent = new Intent();
targetActivityIntent.setComponent(mPrimaryShortPressTargetActivity);
@@ -1578,13 +1566,11 @@
}
private void stemPrimaryDoublePressAction(int behavior) {
+ Slog.d(TAG, "stemPrimaryDoublePressAction: " + behavior);
switch (behavior) {
case DOUBLE_PRESS_PRIMARY_NOTHING:
break;
case DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP:
- if (DEBUG_INPUT) {
- Slog.d(TAG, "Executing stem primary double press action behavior.");
- }
final boolean keyguardActive = mKeyguardDelegate == null
? false
: mKeyguardDelegate.isShowing();
@@ -1596,13 +1582,11 @@
}
private void stemPrimaryTriplePressAction(int behavior) {
+ Slog.d(TAG, "stemPrimaryTriplePressAction: " + behavior);
switch (behavior) {
case TRIPLE_PRESS_PRIMARY_NOTHING:
break;
case TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY:
- if (DEBUG_INPUT) {
- Slog.d(TAG, "Executing stem primary triple press action behavior.");
- }
mTalkbackShortcutController.toggleTalkback(mCurrentUserId);
if (mTalkbackShortcutController.isTalkBackShortcutGestureEnabled()) {
performHapticFeedback(HapticFeedbackConstants.CONFIRM, /* always = */
@@ -1614,9 +1598,7 @@
}
private void stemPrimaryLongPress(long eventTime) {
- if (DEBUG_INPUT) {
- Slog.d(TAG, "Executing stem primary long press action behavior.");
- }
+ Slog.d(TAG, "stemPrimaryLongPress: " + mLongPressOnStemPrimaryBehavior);
switch (mLongPressOnStemPrimaryBehavior) {
case LONG_PRESS_PRIMARY_NOTHING:
@@ -1696,10 +1678,19 @@
return mLongPressOnStemPrimaryBehavior != LONG_PRESS_PRIMARY_NOTHING;
}
+ /** Determine whether the device has any stem primary behaviors. */
private boolean hasStemPrimaryBehavior() {
+ // Read the default stem behaviors from the XML config to determine whether stem primary
+ // behaviors are supported in this build. If they are supported, then the behaviors may be
+ // overridden at runtime through their respective Settings overrides. If they are not
+ // supported, the Settings overrides will not apply.
+ final int defaultShortPressOnStemPrimaryBehavior = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_shortPressOnStemPrimaryBehavior);
+ final int defaultLongPressOnStemPrimaryBehavior = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_longPressOnStemPrimaryBehavior);
return getMaxMultiPressStemPrimaryCount() > 1
- || hasLongPressOnStemPrimaryBehavior()
- || mShortPressOnStemPrimaryBehavior != SHORT_PRESS_PRIMARY_NOTHING;
+ || defaultLongPressOnStemPrimaryBehavior != LONG_PRESS_PRIMARY_NOTHING
+ || defaultShortPressOnStemPrimaryBehavior != SHORT_PRESS_PRIMARY_NOTHING;
}
private void interceptScreenshotChord(int source, long pressDelay) {
@@ -2787,6 +2778,7 @@
KeyEvent.KEYCODE_STEM_PRIMARY,
eventTime,
() -> {
+ Slog.d(TAG, "StemPrimaryKeyRule: executing deferred onKeyUp");
// Save the info of the focused task on screen. This may be used
// later to bring the current focused task back to top. For
// example, stem primary triple press enables the A11y interface
@@ -3526,6 +3518,9 @@
moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event),
true /* leftOrTop */);
logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
+ } else if (event.isAltPressed()) {
+ setSplitscreenFocus(true /* leftOrTop */);
+ logKeyboardSystemsEvent(event, KeyboardLogEvent.CHANGE_SPLITSCREEN_FOCUS);
} else {
logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK);
injectBackGesture(event.getDownTime());
@@ -3534,11 +3529,17 @@
}
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
- moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event),
- false /* leftOrTop */);
- logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
- return true;
+ if (firstDown && event.isMetaPressed()) {
+ if (event.isCtrlPressed()) {
+ moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event),
+ false /* leftOrTop */);
+ logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
+ return true;
+ } else if (event.isAltPressed()) {
+ setSplitscreenFocus(false /* leftOrTop */);
+ logKeyboardSystemsEvent(event, KeyboardLogEvent.CHANGE_SPLITSCREEN_FOCUS);
+ return true;
+ }
}
break;
case KeyEvent.KEYCODE_SLASH:
@@ -4398,6 +4399,13 @@
}
}
+ private void setSplitscreenFocus(boolean leftOrTop) {
+ StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
+ if (statusbar != null) {
+ statusbar.setSplitscreenFocus(leftOrTop);
+ }
+ }
+
void launchHomeFromHotKey(int displayId) {
launchHomeFromHotKey(displayId, true /* awakenFromDreams */, true /*respectKeyguard*/);
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index b50e2bf..6ff8cf3 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -56,6 +56,7 @@
import android.database.ContentObserver;
import android.hardware.SensorManager;
import android.hardware.SystemSensorManager;
+import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.AmbientDisplayConfiguration;
import android.hardware.display.DisplayManagerInternal;
@@ -7152,9 +7153,10 @@
private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
@Override
- public void onStateChanged(int deviceState) {
- if (mDeviceState != deviceState) {
- mDeviceState = deviceState;
+ public void onDeviceStateChanged(@NonNull DeviceState deviceState) {
+ int stateIdentifier = deviceState.getIdentifier();
+ if (mDeviceState != stateIdentifier) {
+ mDeviceState = stateIdentifier;
// Device-state interactions are applied to the default display so that they
// are reflected only with the default power group.
userActivityInternal(Display.DEFAULT_DISPLAY, mClock.uptimeMillis(),
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index c73f89c..f7c236a 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -238,6 +238,14 @@
void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop);
/**
+ * Change the split screen focus to the left / top app or the right / bottom app based on
+ * {@param leftOrTop}.
+ *
+ * @see com.android.internal.statusbar.IStatusBar#setSplitscreenFocus
+ */
+ void setSplitscreenFocus(boolean leftOrTop);
+
+ /**
* Shows the media output switcher dialog.
*
* @param packageName of the session for which the output switcher is shown.
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 214dbe0..7b3e237 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -830,6 +830,15 @@
}
@Override
+ public void setSplitscreenFocus(boolean leftOrTop) {
+ IStatusBar bar = mBar;
+ if (bar != null) {
+ try {
+ bar.setSplitscreenFocus(leftOrTop);
+ } catch (RemoteException ex) { }
+ }
+ }
+ @Override
public void enterDesktop(int displayId) {
IStatusBar bar = mBar;
if (bar != null) {
diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
index 7206c03..ecd140e 100644
--- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
+++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
@@ -89,7 +89,7 @@
@NonNull private final TelephonyManager mTelephonyManager;
@NonNull private final SubscriptionManager mSubscriptionManager;
- @NonNull private final CarrierConfigManager mCarrierConfigManager;
+ @Nullable private final CarrierConfigManager mCarrierConfigManager;
@NonNull private final ActiveDataSubscriptionIdListener mActiveDataSubIdListener;
@@ -158,8 +158,10 @@
mSubscriptionManager.addOnSubscriptionsChangedListener(
executor, mSubscriptionChangedListener);
mTelephonyManager.registerTelephonyCallback(executor, mActiveDataSubIdListener);
- mCarrierConfigManager.registerCarrierConfigChangeListener(executor,
- mCarrierConfigChangeListener);
+ if (mCarrierConfigManager != null) {
+ mCarrierConfigManager.registerCarrierConfigChangeListener(executor,
+ mCarrierConfigChangeListener);
+ }
registerCarrierPrivilegesCallbacks();
}
@@ -200,7 +202,10 @@
mContext.unregisterReceiver(this);
mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionChangedListener);
mTelephonyManager.unregisterTelephonyCallback(mActiveDataSubIdListener);
- mCarrierConfigManager.unregisterCarrierConfigChangeListener(mCarrierConfigChangeListener);
+ if (mCarrierConfigManager != null) {
+ mCarrierConfigManager.unregisterCarrierConfigChangeListener(
+ mCarrierConfigChangeListener);
+ }
unregisterCarrierPrivilegesCallbacks();
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index 0165d65..65ab129 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -314,6 +314,11 @@
wallpaper.wallpaperId = makeWallpaperIdLocked();
}
+ Rect legacyCropHint = new Rect(
+ getAttributeInt(parser, "cropLeft", 0),
+ getAttributeInt(parser, "cropTop", 0),
+ getAttributeInt(parser, "cropRight", 0),
+ getAttributeInt(parser, "cropBottom", 0));
Rect totalCropHint = new Rect(
getAttributeInt(parser, "totalCropLeft", 0),
getAttributeInt(parser, "totalCropTop", 0),
@@ -332,18 +337,19 @@
parser.getAttributeInt(null, "cropBottom" + pair.second, 0));
if (!cropHint.isEmpty()) wallpaper.mCropHints.put(pair.first, cropHint);
}
- if (wallpaper.mCropHints.size() == 0) {
+ if (wallpaper.mCropHints.size() == 0 && totalCropHint.isEmpty()) {
// migration case: the crops per screen orientation are not specified.
- // use the old attributes to find the crop for one screen orientation.
- Integer orientation = totalCropHint.width() < totalCropHint.height()
+ int orientation = legacyCropHint.width() < legacyCropHint.height()
? WallpaperManager.PORTRAIT : WallpaperManager.LANDSCAPE;
- if (!totalCropHint.isEmpty()) wallpaper.mCropHints.put(orientation, totalCropHint);
+ if (!legacyCropHint.isEmpty()) {
+ wallpaper.mCropHints.put(orientation, legacyCropHint);
+ }
} else {
wallpaper.cropHint.set(totalCropHint);
}
wallpaper.mSampleSize = parser.getAttributeFloat(null, "sampleSize", 1f);
} else {
- wallpaper.cropHint.set(totalCropHint);
+ wallpaper.cropHint.set(legacyCropHint);
}
final DisplayData wpData = mWallpaperDisplayHelper
.getDisplayDataOrCreate(DEFAULT_DISPLAY);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0069cdd..6fa6957 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7786,8 +7786,11 @@
@Override
void prepareSurfaces() {
- final boolean show = isVisible() || isAnimating(PARENTS,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS
+ final boolean show = (isVisible()
+ // Ensure that the activity content is hidden when the decor surface is boosted to
+ // prevent UI redressing attack.
+ && !getTask().isDecorSurfaceBoosted())
+ || isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS
| ANIMATION_TYPE_PREDICT_BACK);
if (mSurfaceControl != null) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 2fc6b5f..4ec2f57 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -75,7 +75,7 @@
import static com.android.server.wm.ActivityTaskManagerService.ANIMATE;
import static com.android.server.wm.ActivityTaskManagerService.H.FIRST_SUPERVISOR_TASK_MSG;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
-import static com.android.server.wm.ClientLifecycleManager.shouldDispatchCompatClientTransactionIndependently;
+import static com.android.server.wm.ClientLifecycleManager.shouldDispatchLaunchActivityItemIndependently;
import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_ALLOWLISTED;
import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE;
import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
@@ -953,7 +953,7 @@
}
// Schedule transaction.
- if (shouldDispatchCompatClientTransactionIndependently(r.mTargetSdk)) {
+ if (shouldDispatchLaunchActivityItemIndependently(r.info.packageName, r.getUid())) {
// LaunchActivityItem has @UnsupportedAppUsage usages.
// Guard the bundleClientTransactionFlag feature with targetSDK on Android 15+.
// To not bundle the transaction, dispatch the pending before schedule new
@@ -1478,7 +1478,6 @@
}
mLaunchingActivityWakeLock.release();
}
- mRootWindowContainer.ensureActivitiesVisible();
}
// Atomically retrieve all of the other things to do.
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 1a63f14..5184e49 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -228,11 +228,8 @@
private int mAppTransitionState = APP_STATE_IDLE;
private final ArrayList<AppTransitionListener> mListeners = new ArrayList<>();
- private KeyguardExitAnimationStartListener mKeyguardExitAnimationStartListener;
private final ExecutorService mDefaultExecutor = Executors.newSingleThreadExecutor();
- private final boolean mGridLayoutRecentsEnabled;
-
private final int mDefaultWindowAnimationStyleResId;
private boolean mOverrideTaskTransition;
@@ -249,8 +246,6 @@
mTransitionAnimation = new TransitionAnimation(
context, ProtoLog.isEnabled(WM_DEBUG_ANIM), TAG);
- mGridLayoutRecentsEnabled = SystemProperties.getBoolean("ro.recents.grid", false);
-
final TypedArray windowStyle = mContext.getTheme().obtainStyledAttributes(
com.android.internal.R.styleable.Window);
mDefaultWindowAnimationStyleResId = windowStyle.getResourceId(
@@ -493,11 +488,6 @@
mListeners.remove(listener);
}
- void registerKeygaurdExitAnimationStartListener(
- KeyguardExitAnimationStartListener listener) {
- mKeyguardExitAnimationStartListener = listener;
- }
-
public void notifyAppTransitionFinishedLocked(IBinder token) {
for (int i = 0; i < mListeners.size(); i++) {
mListeners.get(i).onAppTransitionFinishedLocked(token);
@@ -1595,14 +1585,6 @@
return mNextAppTransitionRequests.contains(transit);
}
- /**
- * @return whether the transition should show the thumbnail being scaled down.
- */
- private boolean shouldScaleDownThumbnailTransition(int uiMode, int orientation) {
- return mGridLayoutRecentsEnabled
- || orientation == Configuration.ORIENTATION_PORTRAIT;
- }
-
private void handleAppTransitionTimeout() {
synchronized (mService.mGlobalLock) {
final DisplayContent dc = mDisplayContent;
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 6ed8967..e7fb265 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -642,7 +642,7 @@
// by drawing the rotated content before applying projection transaction of display.
// And it will fade in after the display transition is finished.
if (mTransitionOp == OP_APP_SWITCH && !mIsStartTransactionCommitted
- && canBeAsync(w.mToken)) {
+ && canBeAsync(w.mToken) && !mDisplayContent.hasFixedRotationTransientLaunch()) {
hideImmediately(w.mToken, Operation.ACTION_FADE);
if (DEBUG) Slog.d(TAG, "Hide on finishDrawing " + w.mToken.getTopChild());
}
diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index 816fe1d..e396f2b 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -18,14 +18,19 @@
import android.annotation.NonNull;
import android.app.IApplicationThread;
+import android.app.compat.CompatChanges;
import android.app.servertransaction.ActivityLifecycleItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.ClientTransactionItem;
+import android.app.servertransaction.LaunchActivityItem;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.Trace;
+import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Slog;
@@ -42,6 +47,15 @@
private static final String TAG = "ClientLifecycleManager";
+ /**
+ * To prevent any existing apps from having app compat issue with the non-sdk usages of
+ * {@link ClientTransaction#getActivityToken()}, only allow bundling {@link LaunchActivityItem}
+ * for apps with targetSDK of V and above.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ private static final long ENABLE_BUNDLE_LAUNCH_ACTIVITY_ITEM = 324203798L;
+
/** Mapping from client process binder to its pending transaction. */
@VisibleForTesting
final ArrayMap<IBinder, ClientTransaction> mPendingTransactions = new ArrayMap<>();
@@ -251,16 +265,11 @@
&& !mWms.mWindowPlacerLocked.isInLayout();
}
- /**
- * Guards the bundleClientTransactionFlag feature with targetSDK on Android 15+.
- *
- * Suppressing because it can't guard with @EnabledSince on VANILLA_ICE_CREAM yet since the
- * version is not published.
- *
- * TODO(b/324203798): update in V
- */
- @SuppressWarnings("AndroidFrameworkCompatChange")
- static boolean shouldDispatchCompatClientTransactionIndependently(int appTargetSdk) {
- return appTargetSdk <= Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+ /** Guards bundling {@link LaunchActivityItem} with targetSDK. */
+ static boolean shouldDispatchLaunchActivityItemIndependently(
+ @NonNull String appPackageName, int appUid) {
+ return !CompatChanges.isChangeEnabled(ENABLE_BUNDLE_LAUNCH_ACTIVITY_ITEM,
+ appPackageName,
+ UserHandle.getUserHandleForUid(appUid));
}
}
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index b616d24..8020516 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -157,6 +157,10 @@
}
}
+ void onMirrorOutputSurfaceOrientationChanged() {
+ onConfigurationChanged(mLastOrientation, mLastWindowingMode);
+ }
+
/**
* Handle a configuration change on the display content, and resize recording if needed.
* @param lastOrientation the prior orientation of the configuration
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 837d08b..1dcfde4 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1950,6 +1950,12 @@
&& mFixedRotationLaunchingApp != mFixedRotationTransitionListener.mAnimatingRecents;
}
+ /** It usually means whether the recents activity is launching with a different rotation. */
+ boolean hasFixedRotationTransientLaunch() {
+ return mFixedRotationLaunchingApp != null
+ && mTransitionController.isTransientLaunch(mFixedRotationLaunchingApp);
+ }
+
boolean isFixedRotationLaunchingApp(ActivityRecord r) {
return mFixedRotationLaunchingApp == r;
}
@@ -3515,6 +3521,9 @@
}
void enableHighPerfTransition(boolean enable) {
+ if (!mWmService.mSupportsHighPerfTransitions) {
+ return;
+ }
if (!explicitRefreshRateHints()) {
if (enable) {
getPendingTransaction().setEarlyWakeupStart();
@@ -6835,6 +6844,12 @@
return mContentRecorder;
}
+ void onMirrorOutputSurfaceOrientationChanged() {
+ if (mContentRecorder != null) {
+ mContentRecorder.onMirrorOutputSurfaceOrientationChanged();
+ }
+ }
+
/**
* Pause the recording session.
*/
@@ -7013,9 +7028,8 @@
boolean shouldDeferRotation() {
ActivityRecord source = null;
if (mTransitionController.isShellTransitionsEnabled()) {
- final ActivityRecord r = mFixedRotationLaunchingApp;
- if (r != null && mTransitionController.isTransientLaunch(r)) {
- source = r;
+ if (hasFixedRotationTransientLaunch()) {
+ source = mFixedRotationLaunchingApp;
}
} else if (mAnimatingRecents != null && !hasTopFixedRotationLaunchingApp()) {
source = mAnimatingRecents;
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 384b91a..4a97128 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -19,6 +19,8 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.Display.TYPE_EXTERNAL;
+import static android.view.Display.TYPE_OVERLAY;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
@@ -915,6 +917,11 @@
+ " for " + mDisplayContent);
userRotation = Surface.ROTATION_0;
}
+ final int userRotationOverride = getUserRotationOverride();
+ if (userRotationOverride != 0) {
+ userRotationMode = WindowManagerPolicy.USER_ROTATION_LOCKED;
+ userRotation = userRotationOverride;
+ }
mUserRotationMode = userRotationMode;
mUserRotation = userRotation;
}
@@ -965,6 +972,13 @@
if (changed) {
mService.updateRotation(false /* alwaysSendConfiguration */,
false /* forceRelayout */);
+ // ContentRecorder.onConfigurationChanged and Device.setProjectionLocked are called
+ // during updateRotation above. But onConfigurationChanged is called before
+ // Device.setProjectionLocked, which means that the onConfigurationChanged will
+ // not have the new rotation when it calls getDisplaySurfaceDefaultSize.
+ // To make sure that mirroring takes the new rotation of the output surface
+ // into account we need to call onConfigurationChanged again.
+ mDisplayContent.onMirrorOutputSurfaceOrientationChanged();
}
}
@@ -1780,6 +1794,23 @@
}
}
+ @Surface.Rotation
+ private int getUserRotationOverride() {
+ final int userRotationOverride = SystemProperties.getInt("persist.demo.userrotation",
+ Surface.ROTATION_0);
+ if (userRotationOverride == Surface.ROTATION_0) {
+ return userRotationOverride;
+ }
+
+ final var display = mDisplayContent.mDisplay;
+ if (display.getType() == TYPE_EXTERNAL || display.getType() == TYPE_OVERLAY) {
+ // TODO b/329442350 add chromecast virtual displays here
+ return userRotationOverride;
+ }
+
+ return Surface.ROTATION_0;
+ }
+
@VisibleForTesting
long uptimeMillis() {
return SystemClock.uptimeMillis();
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index dd14642..33588a0 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -364,11 +364,6 @@
com.android.internal.R.integer.config_minNumVisibleRecentTasks_lowRam);
mMaxNumVisibleTasks = res.getInteger(
com.android.internal.R.integer.config_maxNumVisibleRecentTasks_lowRam);
- } else if (SystemProperties.getBoolean("ro.recents.grid", false)) {
- mMinNumVisibleTasks = res.getInteger(
- com.android.internal.R.integer.config_minNumVisibleRecentTasks_grid);
- mMaxNumVisibleTasks = res.getInteger(
- com.android.internal.R.integer.config_maxNumVisibleRecentTasks_grid);
} else {
mMinNumVisibleTasks = res.getInteger(
com.android.internal.R.integer.config_minNumVisibleRecentTasks);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index d87e21c..55dc30c 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3741,7 +3741,9 @@
wc.assignChildLayers(t);
if (!wc.needsZBoost()) {
// Place the decor surface under any untrusted content.
- if (mDecorSurfaceContainer != null && !decorSurfacePlaced
+ if (mDecorSurfaceContainer != null
+ && !mDecorSurfaceContainer.mIsBoosted
+ && !decorSurfacePlaced
&& shouldPlaceDecorSurfaceBelowContainer(wc)) {
mDecorSurfaceContainer.assignLayer(t, layer++);
decorSurfacePlaced = true;
@@ -3760,7 +3762,9 @@
}
// Place the decor surface just above the owner TaskFragment.
- if (mDecorSurfaceContainer != null && !decorSurfacePlaced
+ if (mDecorSurfaceContainer != null
+ && !mDecorSurfaceContainer.mIsBoosted
+ && !decorSurfacePlaced
&& wc == mDecorSurfaceContainer.mOwnerTaskFragment) {
mDecorSurfaceContainer.assignLayer(t, layer++);
decorSurfacePlaced = true;
@@ -3768,10 +3772,10 @@
}
}
- // If not placed yet, the decor surface should be on top of all non-boosted children.
- if (mDecorSurfaceContainer != null && !decorSurfacePlaced) {
+ // Boost the decor surface above other non-boosted windows if requested. The cover surface
+ // will ensure that the content of the windows below are invisible.
+ if (mDecorSurfaceContainer != null && mDecorSurfaceContainer.mIsBoosted) {
mDecorSurfaceContainer.assignLayer(t, layer++);
- decorSurfacePlaced = true;
}
for (int j = 0; j < mChildren.size(); ++j) {
@@ -3796,6 +3800,24 @@
return !isOwnActivity && !isTrustedTaskFragment;
}
+ void setDecorSurfaceBoosted(
+ @NonNull TaskFragment ownerTaskFragment,
+ boolean isBoosted,
+ @Nullable SurfaceControl.Transaction clientTransaction) {
+ if (mDecorSurfaceContainer == null
+ || mDecorSurfaceContainer.mOwnerTaskFragment != ownerTaskFragment) {
+ return;
+ }
+ mDecorSurfaceContainer.setBoosted(isBoosted, clientTransaction);
+ // scheduleAnimation() is called inside assignChildLayers(), which ensures that child
+ // surface visibility is updated with prepareSurfaces()
+ assignChildLayers();
+ }
+
+ boolean isDecorSurfaceBoosted() {
+ return mDecorSurfaceContainer != null && mDecorSurfaceContainer.mIsBoosted;
+ }
+
boolean isTaskId(int taskId) {
return mTaskId == taskId;
}
@@ -6796,14 +6818,35 @@
}
/**
- * A decor surface that is requested by a {@code TaskFragmentOrganizer} which will be placed
- * below children windows except for own Activities and TaskFragment in fully trusted mode.
+ * A class managing the decor surface.
+ *
+ * A decor surface is requested by a {@link TaskFragmentOrganizer} and is placed below children
+ * windows in the Task except for own Activities and TaskFragments in fully trusted mode. The
+ * decor surface is created and shared with the client app with
+ * {@link android.window.TaskFragmentOperation#OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE} and
+ * be removed with
+ * {@link android.window.TaskFragmentOperation#OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE}.
+ *
+ * When boosted with
+ * {@link android.window.TaskFragmentOperation#OP_TYPE_SET_DECOR_SURFACE_BOOSTED}, the decor
+ * surface is placed above all non-boosted windows in the Task, but all the content below it
+ * will be hidden to prevent UI redressing attacks. This can be used by the draggable
+ * divider between {@link TaskFragment}s where veils are drawn on the decor surface while
+ * dragging to indicate new bounds.
*/
@VisibleForTesting
class DecorSurfaceContainer {
+
+ // The container surface is the parent of the decor surface. The container surface
+ // should NEVER be shared with the client. It is used to ensure that the decor surface has
+ // a z-order in the Task that is managed by WM core and cannot be updated by the client
+ // process.
@VisibleForTesting
@NonNull final SurfaceControl mContainerSurface;
+ // The decor surface is shared with the client process owning the
+ // {@link TaskFragmentOrganizer}. It can be used to draw the divider between TaskFragments
+ // or other decorations.
@VisibleForTesting
@NonNull final SurfaceControl mDecorSurface;
@@ -6812,12 +6855,18 @@
@VisibleForTesting
@NonNull TaskFragment mOwnerTaskFragment;
+ private boolean mIsBoosted;
+
+ // The surface transactions that will be applied when the layer is reassigned.
+ @NonNull private final List<SurfaceControl.Transaction> mPendingClientTransactions =
+ new ArrayList<>();
+
private DecorSurfaceContainer(@NonNull TaskFragment initialOwner) {
mOwnerTaskFragment = initialOwner;
mContainerSurface = makeSurface().setContainerLayer()
.setParent(mSurfaceControl)
.setName(mSurfaceControl + " - decor surface container")
- .setEffectLayer()
+ .setContainerLayer()
.setHidden(false)
.setCallsite("Task.DecorSurfaceContainer")
.build();
@@ -6830,14 +6879,28 @@
.build();
}
+ private void setBoosted(
+ boolean isBoosted, @Nullable SurfaceControl.Transaction clientTransaction) {
+ mIsBoosted = isBoosted;
+ // The client transaction will be applied together with the next assignLayer.
+ if (clientTransaction != null) {
+ mDecorSurfaceContainer.mPendingClientTransactions.add(clientTransaction);
+ }
+ }
+
private void assignLayer(@NonNull SurfaceControl.Transaction t, int layer) {
t.setLayer(mContainerSurface, layer);
t.setVisibility(mContainerSurface, mOwnerTaskFragment.isVisible());
+ for (int i = 0; i < mPendingClientTransactions.size(); i++) {
+ t.merge(mPendingClientTransactions.get(i));
+ }
+ mPendingClientTransactions.clear();
}
private void release() {
- mDecorSurface.release();
- mContainerSurface.release();
+ getSyncTransaction()
+ .remove(mDecorSurface)
+ .remove(mContainerSurface);
}
}
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index c919250..3cf561c 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -996,8 +996,7 @@
} else {
// Still have something resumed; can't sleep until it is paused.
ProtoLog.v(WM_DEBUG_STATES, "Sleep needs to pause %s", mResumedActivity);
- startPausing(false /* userLeaving */, true /* uiSleeping */, null /* resuming */,
- "sleep");
+ startPausing(true /* uiSleeping */, null /* resuming */, "sleep");
}
shouldSleep = false;
} else if (mPausingActivity != null) {
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index a84a99a..74dad91 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -671,12 +671,10 @@
if (hasImeSurface) {
if (topActivity.isVisibleRequested() && dc.mInputMethodWindow != null
&& dc.isFixedRotationLaunchingApp(topActivity)) {
- removalInfo.deferRemoveForImeMode = DEFER_MODE_ROTATION;
+ removalInfo.deferRemoveMode = DEFER_MODE_ROTATION;
} else {
- removalInfo.deferRemoveForImeMode = DEFER_MODE_NORMAL;
+ removalInfo.deferRemoveMode = DEFER_MODE_NORMAL;
}
- } else {
- removalInfo.deferRemoveForImeMode = DEFER_MODE_NONE;
}
final WindowState mainWindow =
@@ -745,6 +743,7 @@
removalInfo.taskId = taskId;
removalInfo.windowlessSurface = true;
removalInfo.removeImmediately = immediately;
+ removalInfo.deferRemoveMode = DEFER_MODE_NONE;
try {
lastOrganizer.removeStartingWindow(removalInfo);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 3fb5998..a2f6fb4 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -336,7 +336,7 @@
for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
final WallpaperWindowToken token = mWallpaperTokens.get(i);
token.setVisibility(false);
- if (ProtoLog.isEnabled(WM_DEBUG_WALLPAPER) && token.isVisible()) {
+ if (token.isVisible()) {
ProtoLog.d(WM_DEBUG_WALLPAPER,
"Hiding wallpaper %s from %s target=%s prev=%s callers=%s",
token, winGoingAway, mWallpaperTarget, mPrevWallpaperTarget,
@@ -527,15 +527,15 @@
if ((mLastWallpaperTimeoutTime + WALLPAPER_TIMEOUT_RECOVERY)
< start) {
try {
- if (DEBUG_WALLPAPER) Slog.v(TAG,
- "Waiting for offset complete...");
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "Waiting for offset complete...");
mService.mGlobalLock.wait(WALLPAPER_TIMEOUT);
} catch (InterruptedException e) {
}
- if (DEBUG_WALLPAPER) Slog.v(TAG, "Offset complete!");
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "Offset complete!");
if ((start + WALLPAPER_TIMEOUT) < SystemClock.uptimeMillis()) {
- Slog.i(TAG, "Timeout waiting for wallpaper to offset: "
- + wallpaperWin);
+ ProtoLog.v(WM_DEBUG_WALLPAPER,
+ "Timeout waiting for wallpaper to offset: %s",
+ wallpaperWin);
mLastWallpaperTimeoutTime = start;
}
}
@@ -891,10 +891,6 @@
// The window is visible to the compositor...but is it visible to the user?
// That is what the wallpaper cares about.
final boolean visible = token != null;
- if (DEBUG_WALLPAPER) {
- Slog.v(TAG, "Wallpaper visibility: " + visible + " at display "
- + mDisplayContent.getDisplayId());
- }
if (visible) {
if (mWallpaperTarget.mWallpaperX >= 0) {
@@ -915,10 +911,9 @@
updateWallpaperTokens(visible, mDisplayContent.isKeyguardLocked());
- if (DEBUG_WALLPAPER) {
- Slog.v(TAG, "adjustWallpaperWindows: wallpaper visibility " + visible
- + ", lock visibility " + mDisplayContent.isKeyguardLocked());
- }
+ ProtoLog.v(WM_DEBUG_WALLPAPER,
+ "Wallpaper at display %d - visibility: %b, keyguardLocked: %b",
+ mDisplayContent.getDisplayId(), visible, mDisplayContent.isKeyguardLocked());
if (visible && mLastFrozen != mFindResults.isWallpaperTargetForLetterbox) {
mLastFrozen = mFindResults.isWallpaperTargetForLetterbox;
@@ -927,7 +922,7 @@
/* x= */ 0, /* y= */ 0, /* z= */ 0, /* extras= */ null, /* sync= */ false);
}
- ProtoLog.d(WM_DEBUG_WALLPAPER, "New wallpaper: target=%s prev=%s",
+ ProtoLog.d(WM_DEBUG_WALLPAPER, "Wallpaper target=%s prev=%s",
mWallpaperTarget, mPrevWallpaperTarget);
}
@@ -973,11 +968,9 @@
WALLPAPER_DRAW_PENDING_TIMEOUT_DURATION);
}
- if (DEBUG_WALLPAPER) {
- Slog.v(TAG,
- "Wallpaper should be visible but has not been drawn yet. "
- + "mWallpaperDrawState=" + mWallpaperDrawState);
- }
+ ProtoLog.v(WM_DEBUG_WALLPAPER,
+ "Wallpaper should be visible but has not been drawn yet. "
+ + "mWallpaperDrawState=%d", mWallpaperDrawState);
break;
}
}
@@ -1210,15 +1203,17 @@
boolean isWallpaperTargetForLetterbox = false;
void setTopHideWhenLockedWallpaper(WindowState win) {
- if (DEBUG_WALLPAPER) {
- Slog.v(TAG, "setTopHideWhenLockedWallpaper " + win);
+ if (mTopWallpaper.mTopHideWhenLockedWallpaper != win) {
+ ProtoLog.d(WM_DEBUG_WALLPAPER, "New home screen wallpaper: %s, prev: %s",
+ win, mTopWallpaper.mTopHideWhenLockedWallpaper);
}
mTopWallpaper.mTopHideWhenLockedWallpaper = win;
}
void setTopShowWhenLockedWallpaper(WindowState win) {
- if (DEBUG_WALLPAPER) {
- Slog.v(TAG, "setTopShowWhenLockedWallpaper " + win);
+ if (mTopWallpaper.mTopShowWhenLockedWallpaper != win) {
+ ProtoLog.d(WM_DEBUG_WALLPAPER, "New lock/shared screen wallpaper: %s, prev: %s",
+ win, mTopWallpaper.mTopShowWhenLockedWallpaper);
}
mTopWallpaper.mTopShowWhenLockedWallpaper = win;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 77319cc..acc6330 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -620,14 +620,6 @@
public abstract void unregisterTaskSystemBarsListener(TaskSystemBarsListener listener);
/**
- * Registers a listener to be notified to start the keyguard exit animation.
- *
- * @param listener The listener to register.
- */
- public abstract void registerKeyguardExitAnimationStartListener(
- KeyguardExitAnimationStartListener listener);
-
- /**
* Reports that the password for the given user has changed.
*/
public abstract void reportPasswordChanged(int userId);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 71ffabf..207b1bb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -538,7 +538,7 @@
final boolean mHasPermanentDpad;
final long mDrawLockTimeoutMillis;
final boolean mAllowAnimationsInLowPowerMode;
-
+ final boolean mSupportsHighPerfTransitions;
final boolean mAllowBootMessages;
// Indicates whether the Assistant should show on top of the Dream (respectively, above
@@ -1181,6 +1181,8 @@
com.android.internal.R.bool.config_allowAnimationsInLowPowerMode);
mMaxUiWidth = context.getResources().getInteger(
com.android.internal.R.integer.config_maxUiWidth);
+ mSupportsHighPerfTransitions = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_deviceSupportsHighPerfTransitions);
mDisableTransitionAnimation = context.getResources().getBoolean(
com.android.internal.R.bool.config_disableTransitionAnimation);
mPerDisplayFocusEnabled = context.getResources().getBoolean(
@@ -1192,6 +1194,7 @@
final boolean isScreenSizeDecoupledFromStatusBarAndCutout = context.getResources()
.getBoolean(R.bool.config_decoupleStatusBarAndDisplayCutoutFromScreenSize)
&& mFlags.mAllowsScreenSizeDecoupledFromStatusBarAndCutout;
+
if (mFlags.mInsetsDecoupledConfiguration) {
mDecorTypes = 0;
mConfigTypes = 0;
@@ -8094,15 +8097,6 @@
}
@Override
- public void registerKeyguardExitAnimationStartListener(
- KeyguardExitAnimationStartListener listener) {
- synchronized (mGlobalLock) {
- getDefaultDisplayContentLocked().mAppTransition
- .registerKeygaurdExitAnimationStartListener(listener);
- }
- }
-
- @Override
public void reportPasswordChanged(int userId) {
mKeyguardDisableHandler.updateKeyguardEnabled(userId);
}
@@ -9098,7 +9092,7 @@
Objects.requireNonNull(outInputChannel);
synchronized (mGlobalLock) {
WindowState hostWindowState = hostInputTransferToken != null
- ? mInputToWindowMap.get(hostInputTransferToken.mToken) : null;
+ ? mInputToWindowMap.get(hostInputTransferToken.getToken()) : null;
EmbeddedWindowController.EmbeddedWindow win =
new EmbeddedWindowController.EmbeddedWindow(session, this, clientToken,
hostWindowState, callingUid, callingPid, sanitizedType, displayId,
@@ -9127,12 +9121,13 @@
// If the transferToToken exists in the input to window map, it means the request
// is to transfer from embedded to host. Otherwise, the transferToToken
// represents an embedded window so transfer from host to embedded.
- WindowState windowStateTo = mInputToWindowMap.get(transferToToken.mToken);
+ WindowState windowStateTo = mInputToWindowMap.get(transferToToken.getToken());
if (windowStateTo != null) {
didTransfer = mEmbeddedWindowController.transferToHost(transferFromToken,
windowStateTo);
} else {
- WindowState windowStateFrom = mInputToWindowMap.get(transferFromToken.mToken);
+ WindowState windowStateFrom = mInputToWindowMap.get(
+ transferFromToken.getToken());
didTransfer = mEmbeddedWindowController.transferToEmbedded(windowStateFrom,
transferToToken);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index a6db310f..731184f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -519,6 +519,9 @@
case "default":
fixedToUserRotation = IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT;
break;
+ case "enabled_if_no_auto_rotation":
+ fixedToUserRotation = IWindowManager.FIXED_TO_USER_ROTATION_IF_NO_AUTO_ROTATION;
+ break;
default:
getErrPrintWriter().println("Error: expecting enabled, disabled or default, but we "
+ "get " + arg);
@@ -538,6 +541,9 @@
case IWindowManager.FIXED_TO_USER_ROTATION_DISABLED:
pw.println("disabled");
return 0;
+ case IWindowManager.FIXED_TO_USER_ROTATION_IF_NO_AUTO_ROTATION:
+ pw.println("enabled_if_no_auto_rotation");
+ return 0;
case IWindowManager.FIXED_TO_USER_ROTATION_ENABLED:
pw.println("enabled");
return 0;
@@ -1494,7 +1500,8 @@
pw.println(" Print or set user rotation mode and user rotation.");
pw.println(" dump-visible-window-views");
pw.println(" Dumps the encoded view hierarchies of visible windows");
- pw.println(" fixed-to-user-rotation [-d DISPLAY_ID] [enabled|disabled|default]");
+ pw.println(" fixed-to-user-rotation [-d DISPLAY_ID] [enabled|disabled|default");
+ pw.println(" |enabled_if_no_auto_rotation]");
pw.println(" Print or set rotating display for app requested orientation.");
pw.println(" set-ignore-orientation-request [-d DISPLAY_ID] [true|1|false|0]");
pw.println(" get-ignore-orientation-request [-d DISPLAY_ID] ");
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a63e106..d967cde 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -34,6 +34,7 @@
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_DECOR_SURFACE_BOOSTED;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH;
@@ -124,6 +125,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.android.server.pm.LauncherAppsService.LauncherAppsServiceInternal;
+import com.android.window.flags.Flags;
import java.util.ArrayList;
import java.util.HashMap;
@@ -1557,13 +1559,11 @@
break;
}
case OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE: {
- final Task task = taskFragment.getTask();
- task.moveOrCreateDecorSurfaceFor(taskFragment);
+ taskFragment.getTask().moveOrCreateDecorSurfaceFor(taskFragment);
break;
}
case OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE: {
- final Task task = taskFragment.getTask();
- task.removeDecorSurface();
+ taskFragment.getTask().removeDecorSurface();
break;
}
case OP_TYPE_SET_DIM_ON_TASK: {
@@ -1577,6 +1577,23 @@
operation.isMoveToBottomIfClearWhenLaunch());
break;
}
+ case OP_TYPE_SET_DECOR_SURFACE_BOOSTED: {
+ if (Flags.activityEmbeddingInteractiveDividerFlag()) {
+ final SurfaceControl.Transaction clientTransaction =
+ operation.getSurfaceTransaction();
+ if (clientTransaction != null) {
+ // Sanitize the client transaction. sanitize() silently removes invalid
+ // operations and does not throw or provide signal about whether there are
+ // any invalid operations.
+ clientTransaction.sanitize(caller.mPid, caller.mUid);
+ }
+ taskFragment.getTask().setDecorSurfaceBoosted(
+ taskFragment,
+ operation.getBooleanValue() /* isBoosted */,
+ clientTransaction);
+ }
+ break;
+ }
}
return effects;
}
@@ -1616,19 +1633,6 @@
return false;
}
- // TODO (b/293654166) remove the decor surface checks once we clear security reviews
- if ((opType == OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE
- || opType == OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE)
- && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
- final Throwable exception = new SecurityException(
- "Only a system organizer can perform OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE"
- + " or OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE."
- );
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
- opType, exception);
- return false;
- }
-
if ((opType == OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH)
&& !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
final Throwable exception = new SecurityException(
@@ -1766,6 +1770,13 @@
throw new RuntimeException("Reparenting leaf Tasks is not supported now. " + task);
}
} else {
+ if (hop.getToTop() && task.isRootTask()) {
+ final ActivityRecord pipCandidate = task.findEnterPipOnTaskSwitchCandidate(
+ task.getDisplayArea().getTopRootTask());
+ task.enableEnterPipOnTaskSwitch(pipCandidate, task, null /* toFrontActivity */,
+ null /* options */);
+ }
+
task.getParent().positionChildAt(
hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM,
task, false /* includingParents */);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 46bac16..2b337ae 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5700,7 +5700,8 @@
// window becomes visible while the sync group is still active.
return true;
}
- if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mLastConfigReportedToClient && isDrawn()) {
+ if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mLastConfigReportedToClient && isDrawn()
+ && mPrepareSyncSeqId <= 0) {
// Complete the sync state immediately for a drawn window that doesn't need to redraw.
onSyncFinishedDrawing();
}
diff --git a/services/core/jni/com_android_server_UsbDeviceManager.cpp b/services/core/jni/com_android_server_UsbDeviceManager.cpp
index 0a9ce2f..9dc70af 100644
--- a/services/core/jni/com_android_server_UsbDeviceManager.cpp
+++ b/services/core/jni/com_android_server_UsbDeviceManager.cpp
@@ -108,18 +108,6 @@
return (result == 1);
}
-static jint android_server_UsbDeviceManager_getAudioMode(JNIEnv* /* env */, jobject /* thiz */)
-{
- int fd = open(DRIVER_NAME, O_RDWR);
- if (fd < 0) {
- ALOGE("could not open %s", DRIVER_NAME);
- return false;
- }
- int result = ioctl(fd, ACCESSORY_GET_AUDIO_MODE);
- close(fd);
- return result;
-}
-
static jobject android_server_UsbDeviceManager_openControl(JNIEnv *env, jobject /* thiz */, jstring jFunction) {
ScopedUtfChars function(env, jFunction);
bool ptp = false;
@@ -148,16 +136,13 @@
}
static const JNINativeMethod method_table[] = {
- { "nativeGetAccessoryStrings", "()[Ljava/lang/String;",
- (void*)android_server_UsbDeviceManager_getAccessoryStrings },
- { "nativeOpenAccessory", "()Landroid/os/ParcelFileDescriptor;",
- (void*)android_server_UsbDeviceManager_openAccessory },
- { "nativeIsStartRequested", "()Z",
- (void*)android_server_UsbDeviceManager_isStartRequested },
- { "nativeGetAudioMode", "()I",
- (void*)android_server_UsbDeviceManager_getAudioMode },
- { "nativeOpenControl", "(Ljava/lang/String;)Ljava/io/FileDescriptor;",
- (void*)android_server_UsbDeviceManager_openControl },
+ {"nativeGetAccessoryStrings", "()[Ljava/lang/String;",
+ (void *)android_server_UsbDeviceManager_getAccessoryStrings},
+ {"nativeOpenAccessory", "()Landroid/os/ParcelFileDescriptor;",
+ (void *)android_server_UsbDeviceManager_openAccessory},
+ {"nativeIsStartRequested", "()Z", (void *)android_server_UsbDeviceManager_isStartRequested},
+ {"nativeOpenControl", "(Ljava/lang/String;)Ljava/io/FileDescriptor;",
+ (void *)android_server_UsbDeviceManager_openControl},
};
int register_android_server_UsbDeviceManager(JNIEnv *env)
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index c778398..610fcb5 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -502,7 +502,7 @@
toString(mLocked.pointerGesturesEnabled));
dump += StringPrintf(INDENT "Show Touches: %s\n", toString(mLocked.showTouches));
dump += StringPrintf(INDENT "Pointer Capture: %s, seq=%" PRIu32 "\n",
- mLocked.pointerCaptureRequest.enable ? "Enabled" : "Disabled",
+ mLocked.pointerCaptureRequest.isEnable() ? "Enabled" : "Disabled",
mLocked.pointerCaptureRequest.seq);
if (auto pc = mLocked.legacyPointerController.lock(); pc) {
dump += pc->dump();
@@ -1717,7 +1717,7 @@
return;
}
- ALOGV("%s pointer capture.", request.enable ? "Enabling" : "Disabling");
+ ALOGV("%s pointer capture.", request.isEnable() ? "Enabling" : "Disabling");
mLocked.pointerCaptureRequest = request;
} // release lock
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index b38a2f9..d0df2b2 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -168,6 +168,10 @@
<xs:element type="nonNegativeDecimal" name="screenBrightnessCapForWearBedtimeMode">
<xs:annotation name="final"/>
</xs:element>
+ <!-- Timeout after which we reduce the refresh rate if the screen has been idle, in order to save power. -->
+ <xs:element type="idleScreenRefreshRateTimeout" name="idleScreenRefreshRateTimeout" minOccurs="0">
+ <xs:annotation name="final"/>
+ </xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
@@ -772,6 +776,30 @@
</xs:sequence>
</xs:complexType>
+ <xs:complexType name="idleScreenRefreshRateTimeout">
+ <xs:element name="luxThresholds" type="idleScreenRefreshRateTimeoutLuxThresholds" minOccurs="0">
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:complexType>
+
+ <!-- Lux based timeout after which we reduce the refresh rate if the screen has been idle, in order to save power. -->
+ <xs:complexType name="idleScreenRefreshRateTimeoutLuxThresholds">
+ <xs:sequence>
+ <xs:element name="point" type="idleScreenRefreshRateTimeoutLuxThresholdPoint" maxOccurs="unbounded" />
+ </xs:sequence>
+ </xs:complexType>
+
+ <!-- Represents a tuple of lux and timeout(in ms), which represents the timeout value for the lux in
+ the [luxValue, nextLuxValue (INF if missing)) -->
+ <xs:complexType name="idleScreenRefreshRateTimeoutLuxThresholdPoint">
+ <xs:element name="lux" type="xs:nonNegativeInteger">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <xs:element name="timeout" type="xs:nonNegativeInteger">
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:complexType>
+
<!-- Predefined type names as defined by
AutomaticBrightnessController.AutomaticBrightnessMode -->
<xs:simpleType name="AutoBrightnessModeName">
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index b329db4..00dc908 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -111,6 +111,7 @@
method public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholdsIdle();
method @Nullable public final com.android.server.display.config.HdrBrightnessConfig getHdrBrightnessConfig();
method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode();
+ method public final com.android.server.display.config.IdleScreenRefreshRateTimeout getIdleScreenRefreshRateTimeout();
method public final com.android.server.display.config.SensorDetails getLightSensor();
method public com.android.server.display.config.LuxThrottling getLuxThrottling();
method @Nullable public final String getName();
@@ -146,6 +147,7 @@
method public final void setDisplayBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds);
method public final void setHdrBrightnessConfig(@Nullable com.android.server.display.config.HdrBrightnessConfig);
method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode);
+ method public final void setIdleScreenRefreshRateTimeout(com.android.server.display.config.IdleScreenRefreshRateTimeout);
method public final void setLightSensor(com.android.server.display.config.SensorDetails);
method public void setLuxThrottling(com.android.server.display.config.LuxThrottling);
method public final void setName(@Nullable String);
@@ -222,6 +224,25 @@
method public final void setTransitionPoint_all(@NonNull java.math.BigDecimal);
}
+ public class IdleScreenRefreshRateTimeout {
+ ctor public IdleScreenRefreshRateTimeout();
+ method public final com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholds getLuxThresholds();
+ method public final void setLuxThresholds(com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholds);
+ }
+
+ public class IdleScreenRefreshRateTimeoutLuxThresholdPoint {
+ ctor public IdleScreenRefreshRateTimeoutLuxThresholdPoint();
+ method public final java.math.BigInteger getLux();
+ method public final java.math.BigInteger getTimeout();
+ method public final void setLux(java.math.BigInteger);
+ method public final void setTimeout(java.math.BigInteger);
+ }
+
+ public class IdleScreenRefreshRateTimeoutLuxThresholds {
+ ctor public IdleScreenRefreshRateTimeoutLuxThresholds();
+ method public java.util.List<com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint> getPoint();
+ }
+
public class IntegerArray {
ctor public IntegerArray();
method public java.util.List<java.math.BigInteger> getItem();
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index 96ef2ed..bdea4f9 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -568,7 +568,10 @@
/* clicked_entries */ browsedClickedEntries,
/* provider_of_clicked_entry */ browsedProviderUid,
/* api_status */ apiStatus,
- /* primary_indicated */ finalPhaseMetric.isPrimary()
+ /* primary_indicated */ finalPhaseMetric.isPrimary(),
+ /* oem_credential_manager_ui_uid */ finalPhaseMetric.getOemUiUid(),
+ /* fallback_credential_manager_ui_uid */ finalPhaseMetric.getFallbackUiUid(),
+ /* oem_ui_usage_status */ finalPhaseMetric.getOemUiUsageStatus()
);
} catch (Exception e) {
Slog.w(TAG, "Unexpected error during final no uid metric logging: " + e);
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index cad9a09..c5f2921 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -182,7 +182,7 @@
CredentialProviderInfo info,
String hybridService) {
Slog.i(TAG, "Filtering request options for: " + info.getComponentName());
- if (android.credentials.flags.Flags.hybridFilterFixEnabled()) {
+ if (android.credentials.flags.Flags.hybridFilterOptFixEnabled()) {
ComponentName hybridComponentName = ComponentName.unflattenFromString(hybridService);
if (hybridComponentName != null && hybridComponentName
.equals(info.getComponentName())) {
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
index 468d3c8..9dd6db6 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
@@ -67,6 +67,10 @@
// Other General Information, such as final api status, provider status, entry info, etc...
+ private int mOemUiUid = -1;
+ private int mFallbackUiUid = -1;
+ private OemUiUsageStatus mOemUiUsageStatus = OemUiUsageStatus.UNKNOWN;
+
private int mChosenProviderStatus = -1;
// Indicates if an exception was thrown by this provider, false by default
private boolean mHasException = false;
@@ -302,4 +306,28 @@
public boolean isPrimary() {
return mIsPrimary;
}
+
+ public void setOemUiUid(int oemUiUid) {
+ mOemUiUid = oemUiUid;
+ }
+
+ public int getOemUiUid() {
+ return mOemUiUid;
+ }
+
+ public void setFallbackUiUid(int fallbackUiUid) {
+ mFallbackUiUid = fallbackUiUid;
+ }
+
+ public int getFallbackUiUid() {
+ return mFallbackUiUid;
+ }
+
+ public void setOemUiUsageStatus(OemUiUsageStatus oemUiUsageStatus) {
+ mOemUiUsageStatus = oemUiUsageStatus;
+ }
+
+ public int getOemUiUsageStatus() {
+ return mOemUiUsageStatus.getLoggingInt();
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/OemUiUsageStatus.java b/services/credentials/java/com/android/server/credentials/metrics/OemUiUsageStatus.java
new file mode 100644
index 0000000..2fd3a86
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/OemUiUsageStatus.java
@@ -0,0 +1,42 @@
+/*
+ * 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.credentials.metrics;
+
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_NOT_SPECIFIED;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SPECIFIED_BUT_NOT_FOUND;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SPECIFIED_BUT_NOT_ENABLED;
+
+
+public enum OemUiUsageStatus {
+ UNKNOWN(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_UNKNOWN),
+ SUCCESS(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SUCCESS),
+ FAILURE_NOT_SPECIFIED(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_NOT_SPECIFIED),
+ FAILURE_SPECIFIED_BUT_NOT_FOUND(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SPECIFIED_BUT_NOT_FOUND),
+ FAILURE_SPECIFIED_BUT_NOT_ENABLED(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SPECIFIED_BUT_NOT_ENABLED);
+
+ private final int mLoggingInt;
+
+ OemUiUsageStatus(int loggingInt) {
+ mLoggingInt = loggingInt;
+ }
+
+ public int getLoggingInt() {
+ return mLoggingInt;
+ }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ee72db0..cb87f7e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -79,7 +79,6 @@
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SYSTEM_UPDATES;
-import static android.Manifest.permission.MANAGE_DEVICE_POLICY_THEFT_DETECTION;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_TIME;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING;
@@ -93,6 +92,7 @@
import static android.Manifest.permission.MASTER_CLEAR;
import static android.Manifest.permission.NOTIFY_PENDING_SYSTEM_UPDATE;
import static android.Manifest.permission.QUERY_ADMIN_POLICY;
+import static android.Manifest.permission.QUERY_DEVICE_STOLEN_STATE;
import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY;
import static android.Manifest.permission.SET_TIME;
import static android.Manifest.permission.SET_TIME_ZONE;
@@ -3397,7 +3397,6 @@
}
maybeMigratePoliciesPostUpgradeToDevicePolicyEngineLocked();
migratePoliciesToPolicyEngineLocked();
-
}
maybeStartSecurityLogMonitorOnActivityManagerReady();
break;
@@ -12303,9 +12302,6 @@
}
final CallerIdentity caller = getCallerIdentity(admin);
- // Only allow the system user to use this method
- Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(),
- "createAndManageUser was called from non-system user");
Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_CREATE_AND_MANAGE_USER);
@@ -12316,6 +12312,10 @@
"createAndManageUser was called while in headless single user mode");
}
+ // Only allow the system user to use this method
+ Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(),
+ "createAndManageUser was called from non-system user");
+
final boolean ephemeral = (flags & DevicePolicyManager.MAKE_USER_EPHEMERAL) != 0;
final boolean demo = (flags & DevicePolicyManager.MAKE_USER_DEMO) != 0
&& UserManager.isDeviceInDemoMode(mContext);
@@ -13179,27 +13179,47 @@
CallerIdentity caller, EnforcingAdmin admin, String key, boolean enabled,
boolean parent) {
synchronized (getLockObject()) {
+
+ int ownerType;
if (isDeviceOwner(caller)) {
- if (UserRestrictionsUtils.isGlobal(OWNER_TYPE_DEVICE_OWNER, key)) {
- setGlobalUserRestrictionInternal(admin, key, enabled);
- } else {
- setLocalUserRestrictionInternal(admin, key, enabled, caller.getUserId());
- }
+ ownerType = OWNER_TYPE_DEVICE_OWNER;
+ } else if (isProfileOwnerOfOrganizationOwnedDevice(caller)) {
+ ownerType = OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE;
} else if (isProfileOwner(caller)) {
- if (UserRestrictionsUtils.isGlobal(OWNER_TYPE_PROFILE_OWNER, key)
- || (parent && isProfileOwnerOfOrganizationOwnedDevice(caller)
- && UserRestrictionsUtils.isGlobal(
- OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE, key))) {
- setGlobalUserRestrictionInternal(admin, key, enabled);
- } else {
- int affectedUserId = parent
- ? getProfileParentId(caller.getUserId()) : caller.getUserId();
- setLocalUserRestrictionInternal(admin, key, enabled, affectedUserId);
- }
+ ownerType = OWNER_TYPE_PROFILE_OWNER;
} else {
throw new IllegalStateException("Non-DO/Non-PO cannot set restriction " + key
+ " while targetSdkVersion is less than UPSIDE_DOWN_CAKE");
}
+ setBackwardCompatibleUserRestrictionLocked(ownerType, admin, caller.getUserId(), key,
+ enabled, parent);
+ }
+ }
+
+ private void setBackwardCompatibleUserRestrictionLocked(
+ int ownerType, EnforcingAdmin admin, int userId, String key, boolean enabled,
+ boolean parent) {
+ if (ownerType == OWNER_TYPE_DEVICE_OWNER) {
+ if (UserRestrictionsUtils.isGlobal(OWNER_TYPE_DEVICE_OWNER, key)) {
+ setGlobalUserRestrictionInternal(admin, key, enabled);
+ } else {
+ setLocalUserRestrictionInternal(admin, key, enabled, userId);
+ }
+ } else if (ownerType == OWNER_TYPE_PROFILE_OWNER
+ || ownerType == OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE) {
+ if (UserRestrictionsUtils.isGlobal(OWNER_TYPE_PROFILE_OWNER, key)
+ || (parent && ownerType == OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE
+ && UserRestrictionsUtils.isGlobal(
+ OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE, key))) {
+ setGlobalUserRestrictionInternal(admin, key, enabled);
+ } else {
+ int affectedUserId = parent
+ ? getProfileParentId(userId) : userId;
+ setLocalUserRestrictionInternal(admin, key, enabled, affectedUserId);
+ }
+ } else {
+ throw new IllegalStateException("Non-DO/Non-PO cannot set restriction " + key
+ + " while targetSdkVersion is less than UPSIDE_DOWN_CAKE");
}
}
@@ -18440,7 +18460,7 @@
return;
}
new CalculateHasIncompatibleAccountsTask().executeOnExecutor(
- calculateHasIncompatibleAccountsExecutor, null);
+ calculateHasIncompatibleAccountsExecutor);
}
@Nullable
@@ -22108,12 +22128,12 @@
}
@Override
- public boolean isTheftDetectionTriggered(String callerPackageName) {
+ public boolean isDevicePotentiallyStolen(String callerPackageName) {
final CallerIdentity caller = getCallerIdentity(callerPackageName);
if (!android.app.admin.flags.Flags.deviceTheftImplEnabled()) {
return false;
}
- enforcePermission(MANAGE_DEVICE_POLICY_THEFT_DETECTION, caller.getPackageName(),
+ enforcePermission(QUERY_DEVICE_STOLEN_STATE, caller.getPackageName(),
caller.getUserId());
return mInjector.binderWithCleanCallingIdentity(() ->
@@ -23747,13 +23767,15 @@
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
return mInjector.binderWithCleanCallingIdentity(() -> {
- boolean canForceMigration = forceMigration && !hasNonTestOnlyActiveAdmins();
- if (!canForceMigration && !shouldMigrateV1ToDevicePolicyEngine()) {
- return false;
+ synchronized (getLockObject()) {
+ boolean canForceMigration = forceMigration && !hasNonTestOnlyActiveAdmins();
+ if (!canForceMigration && !shouldMigrateV1ToDevicePolicyEngine()) {
+ return false;
+ }
+ boolean migrated = migrateV1PoliciesToDevicePolicyEngine();
+ migrated &= migratePoliciesPostUpgradeToDevicePolicyEngineLocked();
+ return migrated;
}
- boolean migrated = migrateV1PoliciesToDevicePolicyEngine();
- migrated &= migratePoliciesPostUpgradeToDevicePolicyEngineLocked();
- return migrated;
});
}
@@ -23797,6 +23819,7 @@
try {
migrateScreenCapturePolicyLocked();
migrateLockTaskPolicyLocked();
+ migrateUserRestrictionsLocked();
return true;
} catch (Exception e) {
Slogf.e(LOG_TAG, e, "Error occurred during post upgrade migration to the device "
@@ -24066,6 +24089,42 @@
});
}
+ private void migrateUserRestrictionsLocked() {
+ Binder.withCleanCallingIdentity(() -> {
+ List<UserInfo> users = mUserManager.getUsers();
+ for (UserInfo userInfo : users) {
+ ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(userInfo.id);
+ if (admin == null) continue;
+ ComponentName adminComponent = admin.info.getComponent();
+ int userId = userInfo.id;
+ EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ adminComponent,
+ userId,
+ admin);
+ int ownerType;
+ if (isDeviceOwner(admin)) {
+ ownerType = OWNER_TYPE_DEVICE_OWNER;
+ } else if (isProfileOwnerOfOrganizationOwnedDevice(adminComponent, userId)) {
+ ownerType = OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE;
+ } else if (isProfileOwner(adminComponent, userId)) {
+ ownerType = OWNER_TYPE_PROFILE_OWNER;
+ } else {
+ throw new IllegalStateException("Invalid DO/PO state");
+ }
+
+ for (final String restriction : admin.ensureUserRestrictions().keySet()) {
+ setBackwardCompatibleUserRestrictionLocked(ownerType, enforcingAdmin, userId,
+ restriction, /* enabled */ true, /* parent */ false);
+ }
+ for (final String restriction : admin.getParentActiveAdmin()
+ .ensureUserRestrictions().keySet()) {
+ setBackwardCompatibleUserRestrictionLocked(ownerType, enforcingAdmin, userId,
+ restriction, /* enabled */ true, /* parent */ true);
+ }
+ }
+ });
+ }
+
private List<PackageInfo> getInstalledPackagesOnUser(int userId) {
return mInjector.binderWithCleanCallingIdentity(() ->
mContext.getPackageManager().getInstalledPackagesAsUser(
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 7c669f1..3b2a3dd 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -161,6 +161,7 @@
import com.android.server.notification.NotificationManagerService;
import com.android.server.oemlock.OemLockService;
import com.android.server.om.OverlayManagerService;
+import com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerService;
import com.android.server.os.BugreportManagerService;
import com.android.server.os.DeviceIdentifiersPolicyService;
import com.android.server.os.NativeTombstoneManagerService;
@@ -407,6 +408,8 @@
"com.android.server.searchui.SearchUiManagerService";
private static final String SMARTSPACE_MANAGER_SERVICE_CLASS =
"com.android.server.smartspace.SmartspaceManagerService";
+ private static final String CONTEXTUAL_SEARCH_MANAGER_SERVICE_CLASS =
+ "com.android.server.contextualsearch.ContextualSearchManagerService";
private static final String DEVICE_IDLE_CONTROLLER_CLASS =
"com.android.server.DeviceIdleController";
private static final String BLOB_STORE_MANAGER_SERVICE_CLASS =
@@ -1965,6 +1968,7 @@
startSystemCaptionsManagerService(context, t);
startTextToSpeechManagerService(context, t);
startWearableSensingService(t);
+ startOnDeviceIntelligenceService(t);
if (deviceHasConfigString(
context, R.string.config_defaultAmbientContextDetectionService)) {
@@ -2014,6 +2018,16 @@
Slog.d(TAG, "SmartspaceManagerService not defined by OEM or disabled by flag");
}
+ // Contextual search manager service
+ if (deviceHasConfigString(context,
+ R.string.config_defaultContextualSearchPackageName)) {
+ t.traceBegin("StartContextualSearchService");
+ mSystemServiceManager.startService(CONTEXTUAL_SEARCH_MANAGER_SERVICE_CLASS);
+ t.traceEnd();
+ } else {
+ Slog.d(TAG, "ContextualSearchManagerService not defined or disabled by flag");
+ }
+
t.traceBegin("InitConnectivityModuleConnector");
try {
ConnectivityModuleConnector.getInstance().init(context);
@@ -2166,16 +2180,14 @@
}
t.traceEnd();
- if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)) {
- t.traceBegin("StartVcnManagementService");
- try {
- vcnManagement = VcnManagementService.create(context);
- ServiceManager.addService(Context.VCN_MANAGEMENT_SERVICE, vcnManagement);
- } catch (Throwable e) {
- reportWtf("starting VCN Management Service", e);
- }
- t.traceEnd();
+ t.traceBegin("StartVcnManagementService");
+ try {
+ vcnManagement = VcnManagementService.create(context);
+ ServiceManager.addService(Context.VCN_MANAGEMENT_SERVICE, vcnManagement);
+ } catch (Throwable e) {
+ reportWtf("starting VCN Management Service", e);
}
+ t.traceEnd();
t.traceBegin("StartSystemUpdateManagerService");
try {
@@ -3337,6 +3349,12 @@
t.traceEnd(); // startOtherServices
}
+ private void startOnDeviceIntelligenceService(TimingsTraceAndSlog t) {
+ t.traceBegin("startOnDeviceIntelligenceManagerService");
+ mSystemServiceManager.startService(OnDeviceIntelligenceManagerService.class);
+ t.traceEnd();
+ }
+
/**
* Starts system services defined in apexes.
*
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index af8ce31..7618740 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -268,14 +268,33 @@
} else {
newFlags = newFlags andInv PermissionFlags.RESTRICTION_REVOKED
}
- newFlags =
- if (
- permission.isSoftRestricted && !isExempt &&
- !anyPackageInAppId(appId) {
- permissionName in it.androidPackage!!.requestedPermissions &&
- isSoftRestrictedPermissionExemptForPackage(it, permissionName)
+ val isSoftRestricted =
+ if (permission.isSoftRestricted && !isExempt) {
+ val targetSdkVersion =
+ reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT) {
+ targetSdkVersion,
+ packageState ->
+ if (permissionName in packageState.androidPackage!!.requestedPermissions) {
+ targetSdkVersion.coerceAtMost(
+ packageState.androidPackage!!.targetSdkVersion
+ )
+ } else {
+ targetSdkVersion
+ }
}
- ) {
+ !anyPackageInAppId(appId) {
+ permissionName in it.androidPackage!!.requestedPermissions &&
+ isSoftRestrictedPermissionExemptForPackage(
+ it,
+ targetSdkVersion,
+ permissionName
+ )
+ }
+ } else {
+ false
+ }
+ newFlags =
+ if (isSoftRestricted) {
newFlags or PermissionFlags.SOFT_RESTRICTED
} else {
newFlags andInv PermissionFlags.SOFT_RESTRICTED
@@ -1159,9 +1178,14 @@
}
newFlags =
if (
- permission.isSoftRestricted && !isExempt &&
+ permission.isSoftRestricted &&
+ !isExempt &&
!requestingPackageStates.anyIndexed { _, it ->
- isSoftRestrictedPermissionExemptForPackage(it, permissionName)
+ isSoftRestrictedPermissionExemptForPackage(
+ it,
+ targetSdkVersion,
+ permissionName
+ )
}
) {
newFlags or PermissionFlags.SOFT_RESTRICTED
@@ -1444,13 +1468,20 @@
}
// See also SoftRestrictedPermissionPolicy.mayGrantPermission()
+ // Note: we need the appIdTargetSdkVersion parameter here because we are OR-ing the exempt
+ // status for all packages in a shared UID, but the storage soft restriction logic needs to NOT
+ // exempt when the target SDK version is low, which is the opposite of what most of our code do,
+ // and thus can't check the individual package's target SDK version and rely on the OR among
+ // them.
private fun isSoftRestrictedPermissionExemptForPackage(
packageState: PackageState,
+ appIdTargetSdkVersion: Int,
permissionName: String
): Boolean =
when (permissionName) {
- Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ->
- packageState.androidPackage!!.targetSdkVersion >= Build.VERSION_CODES.Q
+ Manifest.permission.READ_EXTERNAL_STORAGE,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE ->
+ appIdTargetSdkVersion >= Build.VERSION_CODES.Q
else -> false
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt
new file mode 100644
index 0000000..1f3184d
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.display
+
+import android.os.IBinder
+import androidx.test.filters.SmallTest
+import com.android.server.display.brightness.clamper.HdrClamper
+import com.android.server.display.feature.DisplayManagerFlags
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+private const val MAX_BRIGHTNESS = 1.0f
+private const val TRANSITION_POINT = 0.7f
+private const val NORMAL_BRIGHTNESS_HIGH = 0.8f
+private const val NORMAL_BRIGHTNESS_LOW = 0.6f
+
+@SmallTest
+class BrightnessRangeControllerTest {
+
+ private val mockHbmController = mock<HighBrightnessModeController>()
+ private val mockCallback = mock<Runnable>()
+ private val mockConfig = mock<DisplayDeviceConfig>()
+ private val mockNormalBrightnessController = mock<NormalBrightnessModeController>()
+ private val mockHdrClamper = mock<HdrClamper>()
+ private val mockFlags = mock<DisplayManagerFlags>()
+ private val mockToken = mock<IBinder>()
+
+ @Test
+ fun `returns HBC max brightness if HBM supported and ON`() {
+ val controller = createController()
+ assertThat(controller.currentBrightnessMax).isEqualTo(MAX_BRIGHTNESS)
+ }
+
+ @Test
+ fun `returns NBC max brightness if device does not support HBM`() {
+ val controller = createController(hbmSupported = false)
+ assertThat(controller.currentBrightnessMax).isEqualTo(NORMAL_BRIGHTNESS_LOW)
+ }
+
+ @Test
+ fun `returns NBC max brightness if HBM not allowed`() {
+ val controller = createController(hbmAllowed = false)
+ assertThat(controller.currentBrightnessMax).isEqualTo(NORMAL_BRIGHTNESS_LOW)
+ }
+
+ @Test
+ fun `returns HBC max brightness if NBM is disabled`() {
+ val controller = createController(nbmEnabled = false, hbmAllowed = false)
+ assertThat(controller.currentBrightnessMax).isEqualTo(MAX_BRIGHTNESS)
+ }
+
+ @Test
+ fun `returns HBC max brightness if lower than NBC max brightness`() {
+ val controller = createController(
+ hbmAllowed = false,
+ hbmMaxBrightness = TRANSITION_POINT,
+ nbmMaxBrightness = NORMAL_BRIGHTNESS_HIGH
+ )
+ assertThat(controller.currentBrightnessMax).isEqualTo(TRANSITION_POINT)
+ }
+
+ private fun createController(
+ nbmEnabled: Boolean = true,
+ hbmSupported: Boolean = true,
+ hbmAllowed: Boolean = true,
+ hbmMaxBrightness: Float = MAX_BRIGHTNESS,
+ nbmMaxBrightness: Float = NORMAL_BRIGHTNESS_LOW
+ ): BrightnessRangeController {
+ whenever(mockFlags.isNbmControllerEnabled).thenReturn(nbmEnabled)
+ whenever(mockHbmController.deviceSupportsHbm()).thenReturn(hbmSupported)
+ whenever(mockHbmController.isHbmCurrentlyAllowed).thenReturn(hbmAllowed)
+ whenever(mockHbmController.currentBrightnessMax).thenReturn(hbmMaxBrightness)
+ whenever(mockNormalBrightnessController.currentBrightnessMax).thenReturn(nbmMaxBrightness)
+
+ return BrightnessRangeController(mockHbmController, mockCallback, mockConfig,
+ mockNormalBrightnessController, mockHdrClamper, mockFlags, mockToken,
+ DisplayDeviceInfo())
+ }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 2867041..35b69f8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -54,6 +54,7 @@
import com.android.internal.R;
import com.android.server.display.config.HdrBrightnessData;
+import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint;
import com.android.server.display.config.ThermalStatus;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -108,6 +109,7 @@
when(mContext.getResources()).thenReturn(mResources);
when(mFlags.areAutoBrightnessModesEnabled()).thenReturn(true);
when(mFlags.isSensorBasedBrightnessThrottlingEnabled()).thenReturn(true);
+ when(mFlags.isIdleScreenRefreshRateTimeoutEnabled()).thenReturn(true);
mockDeviceConfigs();
}
@@ -146,6 +148,8 @@
assertNull(mDisplayDeviceConfig.getProximitySensor().type);
assertNull(mDisplayDeviceConfig.getProximitySensor().name);
assertEquals(TEMPERATURE_TYPE_SKIN, mDisplayDeviceConfig.getTempSensor().type);
+ assertEquals(List.of(), mDisplayDeviceConfig
+ .getIdleScreenRefreshRateTimeoutLuxThresholdPoint());
assertNull(mDisplayDeviceConfig.getTempSensor().name);
assertTrue(mDisplayDeviceConfig.isAutoBrightnessAvailable());
}
@@ -226,6 +230,19 @@
assertNotNull(mDisplayDeviceConfig.getHostUsiVersion());
assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMajorVersion(), 2);
assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMinorVersion(), 0);
+
+ List<IdleScreenRefreshRateTimeoutLuxThresholdPoint>
+ idleScreenRefreshRateTimeoutLuxThresholdPoints =
+ mDisplayDeviceConfig.getIdleScreenRefreshRateTimeoutLuxThresholdPoint();
+ assertEquals(2, idleScreenRefreshRateTimeoutLuxThresholdPoints.size());
+ assertEquals(6, idleScreenRefreshRateTimeoutLuxThresholdPoints.get(0).getLux()
+ .intValue());
+ assertEquals(1000, idleScreenRefreshRateTimeoutLuxThresholdPoints.get(0)
+ .getTimeout().intValue());
+ assertEquals(10, idleScreenRefreshRateTimeoutLuxThresholdPoints.get(1)
+ .getLux().intValue());
+ assertEquals(800, idleScreenRefreshRateTimeoutLuxThresholdPoints.get(1)
+ .getTimeout().intValue());
}
@Test
@@ -734,6 +751,8 @@
assertEquals(brightnessIntToFloat(35),
mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode(), ZERO_DELTA);
+ assertEquals(List.of(), mDisplayDeviceConfig
+ .getIdleScreenRefreshRateTimeoutLuxThresholdPoint());
}
@Test
@@ -1587,6 +1606,18 @@
+ "<screenBrightnessCapForWearBedtimeMode>"
+ "0.1"
+ "</screenBrightnessCapForWearBedtimeMode>"
+ + "<idleScreenRefreshRateTimeout>"
+ + "<luxThresholds>"
+ + "<point>"
+ + "<lux>6</lux>"
+ + "<timeout>1000</timeout>"
+ + "</point>"
+ + "<point>"
+ + "<lux>10</lux>"
+ + "<timeout>800</timeout>"
+ + "</point>"
+ + "</luxThresholds>"
+ + "</idleScreenRefreshRateTimeout>"
+ "</displayConfiguration>\n";
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index b142334..869cec8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -78,6 +78,7 @@
import android.graphics.Rect;
import android.hardware.Sensor;
import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceState;
import android.hardware.display.BrightnessConfiguration;
import android.hardware.display.BrightnessInfo;
import android.hardware.display.Curve;
@@ -106,6 +107,7 @@
import android.os.UserManager;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
+import android.test.mock.MockContentResolver;
import android.util.SparseArray;
import android.view.ContentRecordingSession;
import android.view.Display;
@@ -123,6 +125,8 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -204,6 +208,8 @@
@Rule
public SetFlagsRule mSetFlagsRule =
new SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
+ @Rule
+ public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
private Context mContext;
@@ -376,6 +382,8 @@
when(display.getBrightnessInfo()).thenReturn(mock(BrightnessInfo.class));
mContext = spy(new ContextWrapper(
ApplicationProvider.getApplicationContext().createDisplayContext(display)));
+ final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
+ when(mContext.getContentResolver()).thenReturn(resolver);
mResources = Mockito.spy(mContext.getResources());
mPowerHandler = new Handler(Looper.getMainLooper());
manageDisplaysPermission(/* granted= */ false);
@@ -714,7 +722,9 @@
IDisplayManagerCallback displayChangesCallback = registerDisplayChangeCallback(
displayManager);
- listener.onStateChanged(123);
+ listener.onDeviceStateChanged(new DeviceState(
+ new DeviceState.Configuration.Builder(123 /* identifier */,
+ "TEST" /* name */).build()));
waitForIdleHandler(handler);
InOrder inOrder = inOrder(mMockWindowManagerInternal, displayChangesCallback);
@@ -2408,6 +2418,7 @@
when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
DisplayManagerInternal localService = displayManager.new LocalService();
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -2440,6 +2451,7 @@
.when(() -> SystemProperties.getBoolean(ENABLE_ON_CONNECT, false));
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
DisplayManagerInternal localService = displayManager.new LocalService();
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -2487,6 +2499,7 @@
when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
@@ -2652,6 +2665,7 @@
when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
DisplayManagerService.BinderService bs = displayManager.new BinderService();
DisplayManagerInternal localService = displayManager.new LocalService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -2699,6 +2713,7 @@
when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 76b7780..fb23213 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1927,6 +1927,8 @@
mock(ScreenOffBrightnessSensorController.class);
final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class);
final HdrClamper hdrClamper = mock(HdrClamper.class);
+ final NormalBrightnessModeController normalBrightnessModeController = mock(
+ NormalBrightnessModeController.class);
BrightnessClamperController clamperController = mock(BrightnessClamperController.class);
when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
@@ -1939,7 +1941,8 @@
TestInjector injector = spy(new TestInjector(displayPowerState, animator,
automaticBrightnessController, wakelockController, brightnessMappingStrategy,
- hysteresisLevels, screenOffBrightnessSensorController, hbmController, hdrClamper,
+ hysteresisLevels, screenOffBrightnessSensorController,
+ hbmController, normalBrightnessModeController, hdrClamper,
clamperController, mDisplayManagerFlagsMock));
final LogicalDisplay display = mock(LogicalDisplay.class);
@@ -2027,6 +2030,8 @@
private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
private final HighBrightnessModeController mHighBrightnessModeController;
+ private final NormalBrightnessModeController mNormalBrightnessModeController;
+
private final HdrClamper mHdrClamper;
private final BrightnessClamperController mClamperController;
@@ -2040,6 +2045,7 @@
HysteresisLevels hysteresisLevels,
ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
HighBrightnessModeController highBrightnessModeController,
+ NormalBrightnessModeController normalBrightnessModeController,
HdrClamper hdrClamper,
BrightnessClamperController clamperController,
DisplayManagerFlags flags) {
@@ -2051,6 +2057,7 @@
mHysteresisLevels = hysteresisLevels;
mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
mHighBrightnessModeController = highBrightnessModeController;
+ mNormalBrightnessModeController = normalBrightnessModeController;
mHdrClamper = hdrClamper;
mClamperController = clamperController;
mFlags = flags;
@@ -2163,7 +2170,8 @@
DisplayDeviceConfig displayDeviceConfig, Handler handler,
DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
return new BrightnessRangeController(hbmController, modeChangeCallback,
- displayDeviceConfig, mHdrClamper, mFlags, displayToken, info);
+ displayDeviceConfig, mNormalBrightnessModeController, mHdrClamper,
+ mFlags, displayToken, info);
}
@Override
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
index 1529a08..1a71e77 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
@@ -228,13 +228,27 @@
@Test
public void testOnExternalDisplayAvailable() {
- when(mMockedLogicalDisplay.isEnabledLocked()).thenReturn(false);
+
mExternalDisplayPolicy.handleExternalDisplayConnectedLocked(mMockedLogicalDisplay);
+ assertNotAskedToEnableDisplay();
+ verify(mMockedExternalDisplayStatsService, never()).onDisplayConnected(any());
+
+ mExternalDisplayPolicy.onBootCompleted();
assertAskedToEnableDisplay();
verify(mMockedExternalDisplayStatsService).onDisplayConnected(eq(mMockedLogicalDisplay));
}
@Test
+ public void testOnExternalDisplayUnpluggedBeforeBootCompletes() {
+ mExternalDisplayPolicy.handleExternalDisplayConnectedLocked(mMockedLogicalDisplay);
+ mExternalDisplayPolicy.handleLogicalDisplayDisconnectedLocked(mMockedLogicalDisplay);
+ mExternalDisplayPolicy.onBootCompleted();
+ assertNotAskedToEnableDisplay();
+ verify(mMockedExternalDisplayStatsService, never()).onDisplayConnected(any());
+ verify(mMockedExternalDisplayStatsService, never()).onDisplayDisconnected(anyInt());
+ }
+
+ @Test
public void testOnExternalDisplayAvailable_criticalThermalCondition()
throws RemoteException {
// Disallow external displays due to thermals.
@@ -303,8 +317,14 @@
mDisplayEventCaptor.capture());
assertThat(mLogicalDisplayCaptor.getValue()).isEqualTo(mMockedLogicalDisplay);
assertThat(mDisplayEventCaptor.getValue()).isEqualTo(EVENT_DISPLAY_CONNECTED);
+ verify(mMockedLogicalDisplay).setEnabledLocked(false);
clearInvocations(mMockedLogicalDisplayMapper);
- when(mMockedLogicalDisplay.isEnabledLocked()).thenReturn(true);
+ clearInvocations(mMockedLogicalDisplay);
+ }
+
+ private void assertNotAskedToEnableDisplay() {
+ verify(mMockedInjector, never()).sendExternalDisplayEventLocked(any(), anyInt());
+ verify(mMockedLogicalDisplay, never()).setEnabledLocked(anyBoolean());
}
private void assertIsExternalDisplayAllowed(final boolean enabled) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 2939192..d0c7077 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -650,7 +650,6 @@
public void testDeviceShouldBePutToSleep() {
assertTrue(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
DEVICE_STATE_OPEN,
- /* isOverrideActive= */false,
/* isInteractive= */true,
/* isBootCompleted= */true));
}
@@ -661,7 +660,6 @@
assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
DEVICE_STATE_OPEN,
- /* isOverrideActive= */false,
/* isInteractive= */true,
/* isBootCompleted= */true));
}
@@ -670,21 +668,10 @@
public void testDeviceShouldNotBePutToSleep() {
assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_OPEN,
DEVICE_STATE_CLOSED,
- /* isOverrideActive= */false,
/* isInteractive= */true,
/* isBootCompleted= */true));
assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
INVALID_DEVICE_STATE_IDENTIFIER,
- /* isOverrideActive= */false,
- /* isInteractive= */true,
- /* isBootCompleted= */true));
- }
-
- @Test
- public void testDeviceShouldNotBePutToSleepDifferentBaseState() {
- assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
- DEVICE_STATE_OPEN,
- /* isOverrideActive= */true,
/* isInteractive= */true,
/* isBootCompleted= */true));
}
@@ -750,7 +737,7 @@
// We can only have one default display
assertEquals(DEFAULT_DISPLAY, id(display1));
- mLogicalDisplayMapper.setDeviceStateLocked(0, false);
+ mLogicalDisplayMapper.setDeviceStateLocked(0);
advanceTime(1000);
// The new state is not applied until the boot is completed
assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
@@ -771,7 +758,7 @@
assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device2)
.getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
- mLogicalDisplayMapper.setDeviceStateLocked(1, false);
+ mLogicalDisplayMapper.setDeviceStateLocked(1);
advanceTime(1000);
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
@@ -784,7 +771,7 @@
mLogicalDisplayMapper.getDisplayLocked(device2)
.getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
- mLogicalDisplayMapper.setDeviceStateLocked(2, false);
+ mLogicalDisplayMapper.setDeviceStateLocked(2);
advanceTime(1000);
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
@@ -861,7 +848,7 @@
// 3) Send DISPLAY_DEVICE_EVENT_CHANGE to inform the mapper of the new display state
// 4) Dispatch handler events.
mLogicalDisplayMapper.onBootCompleted();
- mLogicalDisplayMapper.setDeviceStateLocked(0, false);
+ mLogicalDisplayMapper.setDeviceStateLocked(0);
mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
advanceTime(1000);
final int[] allDisplayIds = mLogicalDisplayMapper.getDisplayIdsLocked(
@@ -891,7 +878,7 @@
/* includeDisabled= */ false));
// Now do it again to go back to state 1
- mLogicalDisplayMapper.setDeviceStateLocked(1, false);
+ mLogicalDisplayMapper.setDeviceStateLocked(1);
mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
advanceTime(1000);
final int[] threeDisplaysEnabled = mLogicalDisplayMapper.getDisplayIdsLocked(
@@ -945,7 +932,7 @@
// We can only have one default display
assertEquals(DEFAULT_DISPLAY, id(display1));
- mLogicalDisplayMapper.setDeviceStateLocked(0, false);
+ mLogicalDisplayMapper.setDeviceStateLocked(0);
advanceTime(1000);
mLogicalDisplayMapper.onBootCompleted();
advanceTime(1000);
@@ -964,11 +951,11 @@
/////////////////
private void finishBootAndFoldDevice() {
- mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_OPEN, false);
+ mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_OPEN);
advanceTime(1000);
mLogicalDisplayMapper.onBootCompleted();
advanceTime(1000);
- mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_CLOSED, false);
+ mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_CLOSED);
advanceTime(1000);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
index 549f0d7..e798aa2 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
@@ -107,8 +107,7 @@
@Test
public void testLetterbox() {
- mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
- /*isAnisotropyCorrectionEnabled=*/ false);
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice);
mDisplayDeviceInfo.xDpi = 0.5f;
mDisplayDeviceInfo.yDpi = 1.0f;
@@ -146,7 +145,8 @@
@Test
public void testNoLetterbox_anisotropyCorrection() {
mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
- /*isAnisotropyCorrectionEnabled=*/ true);
+ /*isAnisotropyCorrectionEnabled=*/ true,
+ /*isAlwaysRotateDisplayDeviceEnabled=*/ true);
// In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
// to using the whole screen. This is because display will rescale it back to fill the
@@ -173,7 +173,8 @@
@Test
public void testLetterbox_anisotropyCorrectionYDpi() {
mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
- /*isAnisotropyCorrectionEnabled=*/ true);
+ /*isAnisotropyCorrectionEnabled=*/ true,
+ /*isAlwaysRotateDisplayDeviceEnabled=*/ true);
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = DISPLAY_WIDTH;
@@ -191,8 +192,7 @@
@Test
public void testPillarbox() {
- mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
- /*isAnisotropyCorrectionEnabled=*/ false);
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice);
mDisplayDeviceInfo.xDpi = 0.5f;
mDisplayDeviceInfo.yDpi = 1.0f;
@@ -230,7 +230,8 @@
@Test
public void testPillarbox_anisotropyCorrection() {
mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
- /*isAnisotropyCorrectionEnabled=*/ true);
+ /*isAnisotropyCorrectionEnabled=*/ true,
+ /*isAlwaysRotateDisplayDeviceEnabled=*/ true);
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = DISPLAY_WIDTH;
@@ -257,7 +258,8 @@
@Test
public void testNoPillarbox_anisotropyCorrectionYDpi() {
mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
- /*isAnisotropyCorrectionEnabled=*/ true);
+ /*isAnisotropyCorrectionEnabled=*/ true,
+ /*isAlwaysRotateDisplayDeviceEnabled=*/ true);
// In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
// to using the whole screen. This is because display will rescale it back to fill the
@@ -315,6 +317,47 @@
}
@Test
+ public void testGetDisplayPositionAlwaysRotateDisplayEnabled() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ true,
+ /*isAlwaysRotateDisplayDeviceEnabled=*/ true);
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+ Point expectedPosition = new Point();
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+ assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
+
+ expectedPosition.set(20, 40);
+ mLogicalDisplay.setDisplayOffsetsLocked(20, 40);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+ assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
+
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = DISPLAY_WIDTH;
+ displayInfo.logicalHeight = DISPLAY_HEIGHT;
+ // Rotation sent from WindowManager is always taken into account by LogicalDisplay
+ // not matter whether FLAG_ROTATES_WITH_CONTENT is set or not.
+ // This is because WindowManager takes care of rotation and expects that LogicalDisplay
+ // will follow the rotation supplied by WindowManager
+ expectedPosition.set(115, -20);
+ displayInfo.rotation = Surface.ROTATION_90;
+ mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+ assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
+
+ expectedPosition.set(40, -20);
+ mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+ displayInfo.logicalWidth = DISPLAY_HEIGHT;
+ displayInfo.logicalHeight = DISPLAY_WIDTH;
+ displayInfo.rotation = Surface.ROTATION_90;
+ mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+ assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
public void testDisplayInputFlags() {
DisplayDevice displayDevice = new DisplayDevice(mDisplayAdapter, mDisplayToken,
"unique_display_id", mContext) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
index 638924e..b182cce 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
@@ -95,9 +95,9 @@
) {
ALL_ENABLED(true, true, CombinedVote(
listOf(DisableRefreshRateSwitchingVote(true),
- SupportedModesVote(
- listOf(SupportedModesVote.SupportedMode(60f, 60f),
- SupportedModesVote.SupportedMode(120f, 120f)))))),
+ SupportedRefreshRatesVote(
+ listOf(SupportedRefreshRatesVote.RefreshRates(60f, 60f),
+ SupportedRefreshRatesVote.RefreshRates(120f, 120f)))))),
VRR_NOT_SUPPORTED(false, true, DisableRefreshRateSwitchingVote(true)),
VSYNC_VOTE_DISABLED(true, false, DisableRefreshRateSwitchingVote(true))
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/ProximitySensorObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/ProximitySensorObserverTest.java
new file mode 100644
index 0000000..e93e5bc
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/ProximitySensorObserverTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.display.mode;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.sensors.SensorManagerInternal;
+
+import junitparams.JUnitParamsRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(JUnitParamsRunner.class)
+public class ProximitySensorObserverTest {
+
+ private static final float FLOAT_TOLERANCE = 0.01f;
+ private static final int DISPLAY_ID = 1;
+ private static final SurfaceControl.RefreshRateRange REFRESH_RATE_RANGE =
+ new SurfaceControl.RefreshRateRange(60, 90);
+
+ private final VotesStorage mStorage = new VotesStorage(() -> { }, null);
+ private final FakesInjector mInjector = new FakesInjector();
+ private ProximitySensorObserver mSensorObserver;
+
+ @Mock
+ DisplayManagerInternal mMockDisplayManagerInternal;
+ @Mock
+ SensorManagerInternal mMockSensorManagerInternal;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mMockDisplayManagerInternal.getRefreshRateForDisplayAndSensor(eq(DISPLAY_ID),
+ any(), any())).thenReturn(REFRESH_RATE_RANGE);
+ mSensorObserver = new ProximitySensorObserver(mStorage, mInjector);
+ mSensorObserver.observe();
+ }
+
+ @Test
+ public void testAddsProximityVoteIfSensorManagerProximityActive() {
+ mSensorObserver.onProximityActive(true);
+
+ SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID);
+ assertThat(displayVotes.size()).isEqualTo(1);
+ Vote vote = displayVotes.get(Vote.PRIORITY_PROXIMITY);
+ assertThat(vote).isNotNull();
+ assertThat(vote).isInstanceOf(CombinedVote.class);
+ CombinedVote combinedVote = (CombinedVote) vote;
+ RefreshRateVote.PhysicalVote physicalVote =
+ (RefreshRateVote.PhysicalVote) combinedVote.mVotes.get(0);
+ assertThat(physicalVote.mMinRefreshRate).isWithin(FLOAT_TOLERANCE).of(60);
+ assertThat(physicalVote.mMaxRefreshRate).isWithin(FLOAT_TOLERANCE).of(90);
+ }
+
+ @Test
+ public void testDoesNotAddProximityVoteIfSensorManagerProximityNotActive() {
+ mSensorObserver.onProximityActive(false);
+
+ SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID);
+ assertThat(displayVotes.size()).isEqualTo(0);
+ }
+
+ @Test
+ public void testDoesNotAddProximityVoteIfDoze() {
+ mInjector.mDozeState = true;
+ mSensorObserver.onDisplayChanged(DISPLAY_ID);
+ mSensorObserver.onProximityActive(true);
+
+ SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID);
+ assertThat(displayVotes.size()).isEqualTo(0);
+ }
+
+ private class FakesInjector extends DisplayModeDirectorTest.FakesInjector {
+
+ private boolean mDozeState = false;
+
+ @Override
+ public Display[] getDisplays() {
+ return new Display[] { createDisplay(DISPLAY_ID) };
+ }
+
+ @Override
+ public DisplayManagerInternal getDisplayManagerInternal() {
+ return mMockDisplayManagerInternal;
+ }
+
+ @Override
+ public SensorManagerInternal getSensorManagerInternal() {
+ return mMockSensorManagerInternal;
+ }
+
+ @Override
+ public boolean isDozeState(Display d) {
+ return mDozeState;
+ }
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
index ebb4f18..230317b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
@@ -85,9 +85,9 @@
internal val expectedVote: Vote?
) {
ALL_ENABLED(true, true, true,
- SupportedModesVote(listOf(
- SupportedModesVote.SupportedMode(60f, 240f),
- SupportedModesVote.SupportedMode(60f, 60f)
+ SupportedRefreshRatesVote(listOf(
+ SupportedRefreshRatesVote.RefreshRates(60f, 240f),
+ SupportedRefreshRatesVote.RefreshRates(60f, 60f)
))),
LOW_POWER_OFF(true, true, false, null),
DVRR_NOT_SUPPORTED_LOW_POWER_ON(false, true, true,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt
index 04e6265..6ce49b8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,12 +27,9 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class SupportedModesVoteTest {
- private val supportedModes = listOf(
- SupportedModesVote.SupportedMode(60f, 90f ),
- SupportedModesVote.SupportedMode(120f, 240f )
- )
+ private val supportedModes = listOf(1, 2, 4)
- private val otherMode = SupportedModesVote.SupportedMode(120f, 120f )
+ private val otherMode = 5
private lateinit var supportedModesVote: SupportedModesVote
@@ -42,31 +39,31 @@
}
@Test
- fun `adds supported modes if supportedModes in summary is null`() {
+ fun `adds supported mode ids if supportedModeIds in summary is null`() {
val summary = createVotesSummary()
supportedModesVote.updateSummary(summary)
- assertThat(summary.supportedModes).containsExactlyElementsIn(supportedModes)
+ assertThat(summary.supportedModeIds).containsExactlyElementsIn(supportedModes)
}
@Test
- fun `does not add supported modes if summary has empty list of modes`() {
+ fun `does not add supported mode ids if summary has empty list of modeIds`() {
val summary = createVotesSummary()
- summary.supportedModes = ArrayList()
+ summary.supportedModeIds = ArrayList()
supportedModesVote.updateSummary(summary)
- assertThat(summary.supportedModes).isEmpty()
+ assertThat(summary.supportedModeIds).isEmpty()
}
@Test
fun `filters out modes that does not match vote`() {
val summary = createVotesSummary()
- summary.supportedModes = ArrayList(listOf(otherMode, supportedModes[0]))
+ summary.supportedModeIds = ArrayList(listOf(otherMode, supportedModes[0]))
supportedModesVote.updateSummary(summary)
- assertThat(summary.supportedModes).containsExactly(supportedModes[0])
+ assertThat(summary.supportedModeIds).containsExactly(supportedModes[0])
}
}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedRefreshRatesVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedRefreshRatesVoteTest.kt
new file mode 100644
index 0000000..d0c112b
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedRefreshRatesVoteTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.mode
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SupportedRefreshRatesVoteTest {
+ private val refreshRates = listOf(
+ SupportedRefreshRatesVote.RefreshRates(60f, 90f),
+ SupportedRefreshRatesVote.RefreshRates(120f, 240f)
+ )
+
+ private val otherMode = SupportedRefreshRatesVote.RefreshRates(120f, 120f)
+
+ private lateinit var supportedRefreshRatesVote: SupportedRefreshRatesVote
+
+ @Before
+ fun setUp() {
+ supportedRefreshRatesVote = SupportedRefreshRatesVote(refreshRates)
+ }
+
+ @Test
+ fun `adds supported refresh rates if supportedModes in summary is null`() {
+ val summary = createVotesSummary()
+
+ supportedRefreshRatesVote.updateSummary(summary)
+
+ assertThat(summary.supportedRefreshRates).containsExactlyElementsIn(refreshRates)
+ }
+
+ @Test
+ fun `does not add supported refresh rates if summary has empty list of refresh rates`() {
+ val summary = createVotesSummary()
+ summary.supportedRefreshRates = ArrayList()
+
+ supportedRefreshRatesVote.updateSummary(summary)
+
+ assertThat(summary.supportedRefreshRates).isEmpty()
+ }
+
+ @Test
+ fun `filters out supported refresh rates that does not match vote`() {
+ val summary = createVotesSummary()
+ summary.supportedRefreshRates = ArrayList(listOf(otherMode, refreshRates[0]))
+
+ supportedRefreshRatesVote.updateSummary(summary)
+
+ assertThat(summary.supportedRefreshRates).containsExactly(refreshRates[0])
+ }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt
new file mode 100644
index 0000000..c49205b
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt
@@ -0,0 +1,212 @@
+/*
+ * 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.display.mode
+
+import android.os.IBinder
+import android.os.RemoteException
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.junit.testparameterinjector.TestParameterInjector
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doThrow
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+private const val DISPLAY_ID = 1
+private const val DISPLAY_ID_OTHER = 2
+
+@SmallTest
+@RunWith(TestParameterInjector::class)
+class SystemRequestObserverTest {
+
+
+ @get:Rule
+ val mockitoRule = MockitoJUnit.rule()
+
+ private val mockToken = mock<IBinder>()
+ private val mockOtherToken = mock<IBinder>()
+
+ private val storage = VotesStorage({}, null)
+
+ @Test
+ fun `requestDisplayModes adds vote to storage`() {
+ val systemRequestObserver = SystemRequestObserver(storage)
+ val requestedModes = intArrayOf(1, 2, 3)
+
+ systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes)
+
+ val votes = storage.getVotes(DISPLAY_ID)
+ assertThat(votes.size()).isEqualTo(1)
+ val vote = votes.get(Vote.PRIORITY_SYSTEM_REQUESTED_MODES)
+ assertThat(vote).isInstanceOf(SupportedModesVote::class.java)
+ val supportedModesVote = vote as SupportedModesVote
+ assertThat(supportedModesVote.mModeIds.size).isEqualTo(requestedModes.size)
+ for (mode in requestedModes) {
+ assertThat(supportedModesVote.mModeIds).contains(mode)
+ }
+ }
+
+ @Test
+ fun `requestDisplayModes overrides votes in storage`() {
+ val systemRequestObserver = SystemRequestObserver(storage)
+
+ systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, intArrayOf(1, 2, 3))
+
+ val overrideModes = intArrayOf(10, 20, 30)
+ systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, overrideModes)
+
+ val votes = storage.getVotes(DISPLAY_ID)
+ assertThat(votes.size()).isEqualTo(1)
+ val vote = votes.get(Vote.PRIORITY_SYSTEM_REQUESTED_MODES)
+ assertThat(vote).isInstanceOf(SupportedModesVote::class.java)
+ val supportedModesVote = vote as SupportedModesVote
+ assertThat(supportedModesVote.mModeIds.size).isEqualTo(overrideModes.size)
+ for (mode in overrideModes) {
+ assertThat(supportedModesVote.mModeIds).contains(mode)
+ }
+ }
+
+ @Test
+ fun `requestDisplayModes removes vote to storage`() {
+ val systemRequestObserver = SystemRequestObserver(storage)
+ val requestedModes = intArrayOf(1, 2, 3)
+
+ systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes)
+ systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, null)
+
+ val votes = storage.getVotes(DISPLAY_ID)
+ assertThat(votes.size()).isEqualTo(0)
+ }
+
+ @Test
+ fun `requestDisplayModes calls linkToDeath to token`() {
+ val systemRequestObserver = SystemRequestObserver(storage)
+ val requestedModes = intArrayOf(1, 2, 3)
+
+ systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes)
+
+ verify(mockToken).linkToDeath(any(), eq(0))
+ }
+
+ @Test
+ fun `does not add votes to storage if binder died when requestDisplayModes called`() {
+ val systemRequestObserver = SystemRequestObserver(storage)
+ val requestedModes = intArrayOf(1, 2, 3)
+
+ doThrow(RemoteException()).whenever(mockOtherToken).linkToDeath(any(), eq(0))
+ systemRequestObserver.requestDisplayModes(mockOtherToken, DISPLAY_ID, requestedModes)
+
+ val votes = storage.getVotes(DISPLAY_ID)
+ assertThat(votes.size()).isEqualTo(0)
+ }
+
+ @Test
+ fun `removes all votes from storage when binder dies`() {
+ val systemRequestObserver = SystemRequestObserver(storage)
+ val requestedModes = intArrayOf(1, 2, 3)
+
+ systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes)
+ val deathRecipientCaptor = argumentCaptor<IBinder.DeathRecipient>()
+ verify(mockToken).linkToDeath(deathRecipientCaptor.capture(), eq(0))
+
+ deathRecipientCaptor.lastValue.binderDied(mockToken)
+
+ val votes = storage.getVotes(DISPLAY_ID)
+ assertThat(votes.size()).isEqualTo(0)
+ }
+
+ @Test
+ fun `calls unlinkToDeath on token when no votes remaining`() {
+ val systemRequestObserver = SystemRequestObserver(storage)
+ val requestedModes = intArrayOf(1, 2, 3)
+
+ systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes)
+ systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, null)
+
+ verify(mockToken).unlinkToDeath(any(), eq(0))
+ }
+
+ @Test
+ fun `does not call unlinkToDeath on token when votes for other display in storage`() {
+ val systemRequestObserver = SystemRequestObserver(storage)
+ val requestedModes = intArrayOf(1, 2, 3)
+
+ systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes)
+ systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID_OTHER, requestedModes)
+ systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, null)
+
+ verify(mockToken, never()).unlinkToDeath(any(), eq(0))
+ }
+
+ @Test
+ fun `requestDisplayModes subset modes from different tokens`() {
+ val systemRequestObserver = SystemRequestObserver(storage)
+ val requestedModes = intArrayOf(1, 2, 3)
+ systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes)
+
+ val requestedOtherModes = intArrayOf(2, 3, 4)
+ systemRequestObserver.requestDisplayModes(mockOtherToken, DISPLAY_ID, requestedOtherModes)
+
+ verify(mockToken).linkToDeath(any(), eq(0))
+ verify(mockOtherToken).linkToDeath(any(), eq(0))
+ verify(mockToken, never()).unlinkToDeath(any(), eq(0))
+ verify(mockOtherToken, never()).unlinkToDeath(any(), eq(0))
+
+ val expectedModes = intArrayOf(2, 3)
+ val votes = storage.getVotes(DISPLAY_ID)
+ assertThat(votes.size()).isEqualTo(1)
+ val vote = votes.get(Vote.PRIORITY_SYSTEM_REQUESTED_MODES)
+ assertThat(vote).isInstanceOf(SupportedModesVote::class.java)
+ val supportedModesVote = vote as SupportedModesVote
+ assertThat(supportedModesVote.mModeIds.size).isEqualTo(expectedModes.size)
+ for (mode in expectedModes) {
+ assertThat(supportedModesVote.mModeIds).contains(mode)
+ }
+ }
+
+ @Test
+ fun `recalculates vote if one binder dies`() {
+ val systemRequestObserver = SystemRequestObserver(storage)
+ val requestedModes = intArrayOf(1, 2, 3)
+ systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes)
+
+ val requestedOtherModes = intArrayOf(2, 3, 4)
+ systemRequestObserver.requestDisplayModes(mockOtherToken, DISPLAY_ID, requestedOtherModes)
+
+ val deathRecipientCaptor = argumentCaptor<IBinder.DeathRecipient>()
+ verify(mockOtherToken).linkToDeath(deathRecipientCaptor.capture(), eq(0))
+ deathRecipientCaptor.lastValue.binderDied(mockOtherToken)
+
+ val votes = storage.getVotes(DISPLAY_ID)
+ assertThat(votes.size()).isEqualTo(1)
+ val vote = votes.get(Vote.PRIORITY_SYSTEM_REQUESTED_MODES)
+ assertThat(vote).isInstanceOf(SupportedModesVote::class.java)
+ val supportedModesVote = vote as SupportedModesVote
+ assertThat(supportedModesVote.mModeIds.size).isEqualTo(requestedModes.size)
+ for (mode in requestedModes) {
+ assertThat(supportedModesVote.mModeIds).contains(mode)
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt
index 910e03c..6b90bde 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt
@@ -18,10 +18,10 @@
internal fun createVotesSummary(
isDisplayResolutionRangeVotingEnabled: Boolean = true,
- vsyncProximityVoteEnabled: Boolean = true,
+ supportedModesVoteEnabled: Boolean = true,
loggingEnabled: Boolean = true,
supportsFrameRateOverride: Boolean = true
): VoteSummary {
- return VoteSummary(isDisplayResolutionRangeVotingEnabled, vsyncProximityVoteEnabled,
+ return VoteSummary(isDisplayResolutionRangeVotingEnabled, supportedModesVoteEnabled,
loggingEnabled, supportsFrameRateOverride)
}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt
index d6c8469..04b35f1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt
@@ -28,29 +28,29 @@
@RunWith(TestParameterInjector::class)
class VoteSummaryTest {
- enum class SupportedModesVoteTestCase(
- val vsyncProximityVoteEnabled: Boolean,
- internal val summarySupportedModes: List<SupportedModesVote.SupportedMode>?,
+ enum class SupportedRefreshRatesTestCase(
+ val supportedModesVoteEnabled: Boolean,
+ internal val summaryRefreshRates: List<SupportedRefreshRatesVote.RefreshRates>?,
val modesToFilter: Array<Display.Mode>,
val expectedModeIds: List<Int>
) {
HAS_NO_MATCHING_VOTE(true,
- listOf(SupportedModesVote.SupportedMode(60f, 60f)),
+ listOf(SupportedRefreshRatesVote.RefreshRates(60f, 60f)),
arrayOf(createMode(1, 90f, 90f),
createMode(2, 90f, 60f),
createMode(3, 60f, 90f)),
listOf()
),
HAS_SINGLE_MATCHING_VOTE(true,
- listOf(SupportedModesVote.SupportedMode(60f, 90f)),
+ listOf(SupportedRefreshRatesVote.RefreshRates(60f, 90f)),
arrayOf(createMode(1, 90f, 90f),
createMode(2, 90f, 60f),
createMode(3, 60f, 90f)),
listOf(3)
),
HAS_MULTIPLE_MATCHING_VOTES(true,
- listOf(SupportedModesVote.SupportedMode(60f, 90f),
- SupportedModesVote.SupportedMode(90f, 90f)),
+ listOf(SupportedRefreshRatesVote.RefreshRates(60f, 90f),
+ SupportedRefreshRatesVote.RefreshRates(90f, 90f)),
arrayOf(createMode(1, 90f, 90f),
createMode(2, 90f, 60f),
createMode(3, 60f, 90f)),
@@ -70,7 +70,69 @@
createMode(3, 60f, 90f)),
listOf(1, 2, 3)
),
- HAS_VSYNC_PROXIMITY_DISABLED(false,
+ HAS_SUPPORTED_MODES_VOTE_DISABLED(false,
+ listOf(),
+ arrayOf(createMode(1, 90f, 90f),
+ createMode(2, 90f, 60f),
+ createMode(3, 60f, 90f)),
+ listOf(1, 2, 3)
+ ),
+ }
+
+ @Test
+ fun `filters modes for summary supportedRefreshRates`(
+ @TestParameter testCase: SupportedRefreshRatesTestCase
+ ) {
+ val summary = createSummary(testCase.supportedModesVoteEnabled)
+ summary.supportedRefreshRates = testCase.summaryRefreshRates
+
+ val result = summary.filterModes(testCase.modesToFilter)
+
+ assertThat(result.map { it.modeId }).containsExactlyElementsIn(testCase.expectedModeIds)
+ }
+
+ enum class SupportedModesTestCase(
+ val supportedModesVoteEnabled: Boolean,
+ internal val summarySupportedModes: List<Int>?,
+ val modesToFilter: Array<Display.Mode>,
+ val expectedModeIds: List<Int>
+ ) {
+ HAS_NO_MATCHING_VOTE(true,
+ listOf(4, 5),
+ arrayOf(createMode(1, 90f, 90f),
+ createMode(2, 90f, 60f),
+ createMode(3, 60f, 90f)),
+ listOf()
+ ),
+ HAS_SINGLE_MATCHING_VOTE(true,
+ listOf(3),
+ arrayOf(createMode(1, 90f, 90f),
+ createMode(2, 90f, 60f),
+ createMode(3, 60f, 90f)),
+ listOf(3)
+ ),
+ HAS_MULTIPLE_MATCHING_VOTES(true,
+ listOf(1, 3),
+ arrayOf(createMode(1, 90f, 90f),
+ createMode(2, 90f, 60f),
+ createMode(3, 60f, 90f)),
+ listOf(1, 3)
+ ),
+ HAS_NO_SUPPORTED_MODES(true,
+ listOf(),
+ arrayOf(createMode(1, 90f, 90f),
+ createMode(2, 90f, 60f),
+ createMode(3, 60f, 90f)),
+ listOf()
+ ),
+ HAS_NULL_SUPPORTED_MODES(true,
+ null,
+ arrayOf(createMode(1, 90f, 90f),
+ createMode(2, 90f, 60f),
+ createMode(3, 60f, 90f)),
+ listOf(1, 2, 3)
+ ),
+ HAS_SUPPORTED_MODES_VOTE_DISABLED(false,
listOf(),
arrayOf(createMode(1, 90f, 90f),
createMode(2, 90f, 60f),
@@ -81,10 +143,10 @@
@Test
fun `filters modes for summary supportedModes`(
- @TestParameter testCase: SupportedModesVoteTestCase
+ @TestParameter testCase: SupportedModesTestCase
) {
- val summary = createSummary(testCase.vsyncProximityVoteEnabled)
- summary.supportedModes = testCase.summarySupportedModes
+ val summary = createSummary(testCase.supportedModesVoteEnabled)
+ summary.supportedModeIds = testCase.summarySupportedModes
val result = summary.filterModes(testCase.modesToFilter)
@@ -96,8 +158,8 @@
FloatArray(0), IntArray(0))
}
-private fun createSummary(vsyncVoteEnabled: Boolean): VoteSummary {
- val summary = createVotesSummary(vsyncProximityVoteEnabled = vsyncVoteEnabled)
+private fun createSummary(supportedModesVoteEnabled: Boolean): VoteSummary {
+ val summary = createVotesSummary(supportedModesVoteEnabled = supportedModesVoteEnabled)
summary.width = 600
summary.height = 800
summary.maxPhysicalRefreshRate = Float.POSITIVE_INFINITY
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java
index 1f6f1a4..a248d6de 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -153,4 +154,51 @@
assertThat(mVotesStorage.getVotes(DISPLAY_ID).size()).isEqualTo(0);
verify(mVotesListener, never()).onChanged();
}
+
+
+ @Test
+ public void removesAllVotesForPriority() {
+ // GIVEN vote storage with votes
+ mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE);
+ mVotesStorage.updateVote(DISPLAY_ID, PRIORITY_OTHER, VOTE_OTHER);
+ mVotesStorage.updateVote(DISPLAY_ID_OTHER, PRIORITY, VOTE);
+ mVotesStorage.updateVote(DISPLAY_ID_OTHER, PRIORITY_OTHER, VOTE_OTHER);
+ // WHEN removeAllVotesForPriority is called
+ mVotesStorage.removeAllVotesForPriority(PRIORITY);
+ // THEN votes with priority are removed from the storage
+ SparseArray<Vote> votes = mVotesStorage.getVotes(DISPLAY_ID);
+ assertThat(votes.size()).isEqualTo(1);
+ assertThat(votes.get(PRIORITY)).isNull();
+ votes = mVotesStorage.getVotes(DISPLAY_ID_OTHER);
+ assertThat(votes.size()).isEqualTo(1);
+ assertThat(votes.get(PRIORITY)).isNull();
+ }
+
+ @Test
+ public void removesAllVotesForPriority_notifiesListenerOnce() {
+ // GIVEN vote storage with votes
+ mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE);
+ mVotesStorage.updateVote(DISPLAY_ID, PRIORITY_OTHER, VOTE_OTHER);
+ mVotesStorage.updateVote(DISPLAY_ID_OTHER, PRIORITY, VOTE);
+ mVotesStorage.updateVote(DISPLAY_ID_OTHER, PRIORITY_OTHER, VOTE_OTHER);
+ clearInvocations(mVotesListener);
+ // WHEN removeAllVotesForPriority is called
+ mVotesStorage.removeAllVotesForPriority(PRIORITY);
+ // THEN listener notified once
+ verify(mVotesListener).onChanged();
+ }
+
+ @Test
+ public void removesAllVotesForPriority_noChangesIfNothingRemoved() {
+ // GIVEN vote storage with votes
+ mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE);
+ clearInvocations(mVotesListener);
+ // WHEN removeAllVotesForPriority is called for missing priority
+ mVotesStorage.removeAllVotesForPriority(PRIORITY_OTHER);
+ // THEN no changes to votes storage
+ SparseArray<Vote> votes = mVotesStorage.getVotes(DISPLAY_ID);
+ assertThat(votes.size()).isEqualTo(1);
+ assertThat(votes.get(PRIORITY)).isEqualTo(VOTE);
+ verify(mVotesListener, never()).onChanged();
+ }
}
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 fcb3caa..dc5b00a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -28,10 +28,15 @@
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
+import static android.os.PowerExemptionManager.REASON_DENIED;
import static android.util.DebugUtils.valueToString;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.am.ActivityManagerInternalTest.CustomThread;
import static com.android.server.am.ActivityManagerService.Injector;
import static com.android.server.am.ProcessList.NETWORK_STATE_BLOCK;
@@ -52,28 +57,40 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
import android.Manifest;
import android.app.ActivityManager;
+import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.app.BackgroundStartPrivileges;
import android.app.BroadcastOptions;
+import android.app.ForegroundServiceDelegationOptions;
import android.app.IApplicationThread;
import android.app.IUidObserver;
+import android.app.Notification;
+import android.app.NotificationChannel;
import android.app.SyncNotedAppOp;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.ServiceInfo;
+import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
@@ -84,6 +101,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.permission.IPermissionManager;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
@@ -105,18 +123,20 @@
import com.android.server.am.ProcessList.IsolatedUidRangeAllocator;
import com.android.server.am.UidObserverController.ChangeRecord;
import com.android.server.appop.AppOpsService;
+import com.android.server.notification.NotificationManagerInternal;
+import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.ActivityTaskManagerService;
import org.junit.After;
-import org.junit.AfterClass;
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.mockito.verification.VerificationMode;
import java.io.File;
import java.util.ArrayList;
@@ -127,13 +147,15 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.function.Function;
/**
* Test class for {@link ActivityManagerService}.
*
* Build/Install/Run:
- * atest FrameworksServicesTests:ActivityManagerServiceTest
+ * atest FrameworksMockingServicesTests:ActivityManagerServiceTest
*/
@Presubmit
@SmallTest
@@ -148,6 +170,9 @@
private static final String TEST_EXTRA_KEY1 = "com.android.server.am.TEST_EXTRA_KEY1";
private static final String TEST_EXTRA_VALUE1 = "com.android.server.am.TEST_EXTRA_VALUE1";
+
+ private static final String TEST_PACKAGE_NAME = "com.android.server.am.testpackage";
+
private static final String PROPERTY_APPLY_SDK_SANDBOX_AUDIT_RESTRICTIONS =
"apply_sdk_sandbox_audit_restrictions";
private static final String PROPERTY_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS =
@@ -155,6 +180,7 @@
private static final String APPLY_SDK_SANDBOX_AUDIT_RESTRICTIONS = ":isSdkSandboxAudit";
private static final String APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS = ":isSdkSandboxNext";
private static final int TEST_UID = 11111;
+ private static final int TEST_PID = 22222;
private static final int USER_ID = 666;
private static final long TEST_PROC_STATE_SEQ1 = 555;
@@ -169,22 +195,8 @@
UidRecord.CHANGE_CAPABILITY,
};
- private static PackageManagerInternal sPackageManagerInternal;
private static ProcessList.ProcessListSettingsListener sProcessListSettingsListener;
- @BeforeClass
- public static void setUpOnce() {
- sPackageManagerInternal = mock(PackageManagerInternal.class);
- doReturn(new ComponentName("", "")).when(sPackageManagerInternal)
- .getSystemUiServiceComponent();
- LocalServices.addService(PackageManagerInternal.class, sPackageManagerInternal);
- }
-
- @AfterClass
- public static void tearDownOnce() {
- LocalServices.removeServiceForTest(PackageManagerInternal.class);
- }
-
@Rule
public final ApplicationExitInfoTest.ServiceThreadRule
mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
@@ -196,15 +208,41 @@
@Mock private AppOpsService mAppOpsService;
@Mock private UserController mUserController;
+ @Mock private IPackageManager mPackageManager;
+ @Mock private IPermissionManager mPermissionManager;
+ @Mock private BatteryStatsService mBatteryStatsService;
+ @Mock private PackageManagerInternal mPackageManagerInternal;
+ @Mock private ActivityTaskManagerInternal mActivityTaskManagerInternal;
+ @Mock private NotificationManagerInternal mNotificationManagerInternal;
+
private TestInjector mInjector;
private ActivityManagerService mAms;
+ private ActiveServices mActiveServices;
private HandlerThread mHandlerThread;
private TestHandler mHandler;
+ private MockitoSession mMockingSession;
+
@Before
- public void setUp() {
+ public void setUp() throws Exception {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .mockStatic(AppGlobals.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
MockitoAnnotations.initMocks(this);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
+ LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInternal);
+ LocalServices.addService(NotificationManagerInternal.class, mNotificationManagerInternal);
+
+ doReturn(new ComponentName("", "")).when(mPackageManagerInternal)
+ .getSystemUiServiceComponent();
+
+ doReturn(mPackageManager).when(AppGlobals::getPackageManager);
+ doReturn(mPermissionManager).when(AppGlobals::getPermissionManager);
+ doReturn(new String[]{""}).when(mPackageManager).getPackagesForUid(eq(Process.myUid()));
+
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
mHandler = new TestHandler(mHandlerThread.getLooper());
@@ -220,6 +258,7 @@
mAms.mConstants.mNetworkAccessTimeoutMs = 2000;
mAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
mAms.mActivityTaskManager.initialize(null, null, mHandler.getLooper());
+ mAms.mAtmInternal = mActivityTaskManagerInternal;
mHandler.setRunnablesToIgnore(
List.of(mAms.mUidObserverController.getDispatchRunnableForTest()));
@@ -250,6 +289,15 @@
if (sProcessListSettingsListener != null) {
sProcessListSettingsListener.unregisterObserver();
}
+ clearInvocations(mNotificationManagerInternal);
+
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
+ LocalServices.removeServiceForTest(NotificationManagerInternal.class);
+
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
}
@SuppressWarnings("GuardedBy")
@@ -445,6 +493,7 @@
}
}
+ @SuppressWarnings("GuardedBy")
private UidRecord addUidRecord(int uid) {
final UidRecord uidRec = new UidRecord(uid, mAms);
uidRec.procStateSeqWaitingForNetwork = 1;
@@ -453,10 +502,13 @@
ApplicationInfo info = new ApplicationInfo();
info.packageName = "";
+ info.uid = uid;
final ProcessRecord appRec = new ProcessRecord(mAms, info, TAG, uid);
- final ProcessStatsService tracker = new ProcessStatsService(mAms, mContext.getCacheDir());
- appRec.makeActive(mock(IApplicationThread.class), tracker);
+ final ProcessStatsService tracker = mAms.mProcessStats;
+ final IApplicationThread appThread = mock(IApplicationThread.class);
+ doReturn(mock(IBinder.class)).when(appThread).asBinder();
+ appRec.makeActive(appThread, tracker);
mAms.mProcessList.getLruProcessesLSP().add(appRec);
return uidRec;
@@ -1209,6 +1261,108 @@
mAms.mUidObserverController.getPendingUidChangesForTest().clear();
}
+ @Test
+ public void testStartForegroundServiceDelegateWithNotification() throws Exception {
+ testStartForegroundServiceDelegate(true);
+ }
+
+ @Test
+ public void testStartForegroundServiceDelegateWithoutNotification() throws Exception {
+ testStartForegroundServiceDelegate(false);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ private void testStartForegroundServiceDelegate(boolean withNotification) throws Exception {
+ mockNoteOperation();
+
+ final int notificationId = 42;
+ final Notification notification = mock(Notification.class);
+
+ addUidRecord(TEST_UID);
+ final ProcessRecord app = mAms.mProcessList.getLruProcessesLSP().get(0);
+ app.mPid = TEST_PID;
+ app.info.packageName = TEST_PACKAGE_NAME;
+ app.info.processName = TEST_PACKAGE_NAME;
+
+ doReturn(app.info).when(mPackageManager).getApplicationInfo(
+ eq(app.info.packageName), anyLong(), anyInt());
+
+ doReturn(true).when(mActiveServices)
+ .canStartForegroundServiceLocked(anyInt(), anyInt(), anyString());
+ doReturn(REASON_DENIED).when(mActiveServices)
+ .shouldAllowFgsWhileInUsePermissionLocked(anyString(), anyInt(), anyInt(),
+ any(ProcessRecord.class), any(BackgroundStartPrivileges.class));
+
+ doReturn(true).when(mNotificationManagerInternal).areNotificationsEnabledForPackage(
+ anyString(), anyInt());
+ doReturn(mock(Icon.class)).when(notification).getSmallIcon();
+ doReturn("").when(notification).getChannelId();
+ doReturn(mock(NotificationChannel.class)).when(mNotificationManagerInternal)
+ .getNotificationChannel(anyString(), anyInt(), anyString());
+
+ mAms.mAppProfiler.mCachedAppsWatermarkData.mCachedAppHighWatermark = Integer.MAX_VALUE;
+
+ final ForegroundServiceDelegationOptions.Builder optionsBuilder =
+ new ForegroundServiceDelegationOptions.Builder()
+ .setClientPid(app.mPid)
+ .setClientUid(app.uid)
+ .setClientPackageName(app.info.packageName)
+ .setClientAppThread(app.getThread())
+ .setSticky(false)
+ .setClientInstanceName(
+ "SystemExemptedFgsDelegate_"
+ + Process.myUid()
+ + "_"
+ + app.uid
+ + "_"
+ + app.info.packageName)
+ .setForegroundServiceTypes(ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED)
+ .setDelegationService(
+ ForegroundServiceDelegationOptions.DELEGATION_SERVICE_SYSTEM_EXEMPTED);
+ if (withNotification) {
+ optionsBuilder.setClientNotification(notificationId, notification);
+ }
+ final ForegroundServiceDelegationOptions options = optionsBuilder.build();
+
+ final CountDownLatch[] latchHolder = new CountDownLatch[1];
+ final ServiceConnection conn = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ latchHolder[0].countDown();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ latchHolder[0].countDown();
+ }
+ };
+
+ latchHolder[0] = new CountDownLatch(1);
+ mAms.mInternal.startForegroundServiceDelegate(options, conn);
+
+ assertThat(latchHolder[0].await(5, TimeUnit.SECONDS)).isTrue();
+ assertEquals(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
+ app.mState.getCurProcState());
+ final long timeoutMs = 5000L;
+ final VerificationMode mode = withNotification
+ ? timeout(timeoutMs) : after(timeoutMs).atMost(0);
+ verify(mNotificationManagerInternal, mode)
+ .enqueueNotification(eq(app.info.packageName), eq(app.info.packageName),
+ eq(app.info.uid), eq(app.mPid), eq(null),
+ eq(notificationId), eq(notification), anyInt(), eq(true));
+
+ latchHolder[0] = new CountDownLatch(1);
+ mAms.mInternal.stopForegroundServiceDelegate(options);
+
+ assertThat(latchHolder[0].await(5, TimeUnit.SECONDS)).isTrue();
+ assertEquals(ActivityManager.PROCESS_STATE_CACHED_EMPTY,
+ app.mState.getCurProcState());
+ verify(mNotificationManagerInternal, mode)
+ .cancelNotification(eq(app.info.packageName), eq(app.info.packageName),
+ eq(app.info.uid), eq(app.mPid), eq(null),
+ eq(notificationId), anyInt());
+ }
+
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
@@ -1291,6 +1445,19 @@
usersStartedOnSecondaryDisplays.add(new Pair<>(userId, displayId));
return returnValueForstartUserOnSecondaryDisplay;
}
+
+ @Override
+ public ActiveServices getActiveServices(ActivityManagerService service) {
+ if (mActiveServices == null) {
+ mActiveServices = spy(new ActiveServices(service));
+ }
+ return mActiveServices;
+ }
+
+ @Override
+ public BatteryStatsService getBatteryStatsService() {
+ return mBatteryStatsService;
+ }
}
// TODO: [b/302724778] Remove manual JNI load
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index bc7c9a5..6aa1825 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -582,7 +582,8 @@
@Test
public void testAutoLockPrivateProfile() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
UserManagerService mSpiedUms = spy(mUms);
UserInfo privateProfileUser =
mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
@@ -600,7 +601,8 @@
@Test
public void testAutoLockOnDeviceLockForPrivateProfile() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
UserManagerService mSpiedUms = spy(mUms);
UserInfo privateProfileUser =
@@ -620,7 +622,8 @@
@Test
public void testAutoLockOnDeviceLockForPrivateProfile_keyguardUnlocked() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
UserManagerService mSpiedUms = spy(mUms);
UserInfo privateProfileUser =
@@ -638,7 +641,8 @@
@Test
public void testAutoLockOnDeviceLockForPrivateProfile_flagDisabled() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.disableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
UserManagerService mSpiedUms = spy(mUms);
UserInfo privateProfileUser =
@@ -657,7 +661,8 @@
@Test
public void testAutoLockAfterInactityForPrivateProfile() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
UserManagerService mSpiedUms = spy(mUms);
mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
@@ -679,7 +684,8 @@
@Test
public void testSetOrUpdateAutoLockPreference_noPrivateProfile() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
@@ -693,7 +699,8 @@
@Test
public void testSetOrUpdateAutoLockPreference() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
@@ -743,6 +750,8 @@
@Test
public void testGetProfileIdsExcludingHidden() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_HIDING_PROFILES);
UserInfo privateProfileUser =
mUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
@@ -754,7 +763,8 @@
@Test
public void testCreatePrivateProfileOnHeadlessSystemUser_shouldAllowCreation() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
UserManagerService mSpiedUms = spy(mUms);
int mainUser = mSpiedUms.getMainUserId();
@@ -766,7 +776,8 @@
@Test
public void testCreatePrivateProfileOnSecondaryUser_shouldNotAllowCreation() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
UserInfo user = mUms.createUserWithThrow(generateLongString(), USER_TYPE_FULL_SECONDARY, 0);
assertThat(mUms.canAddPrivateProfile(user.id)).isFalse();
@@ -777,7 +788,8 @@
@Test
public void testCreatePrivateProfileOnAutoDevices_shouldNotAllowCreation() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_AUTOMOTIVE), anyInt());
int mainUser = mUms.getMainUserId();
@@ -789,7 +801,8 @@
@Test
public void testCreatePrivateProfileOnTV_shouldNotAllowCreation() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_LEANBACK), anyInt());
int mainUser = mUms.getMainUserId();
@@ -801,7 +814,8 @@
@Test
public void testCreatePrivateProfileOnEmbedded_shouldNotAllowCreation() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_EMBEDDED), anyInt());
int mainUser = mUms.getMainUserId();
@@ -813,7 +827,8 @@
@Test
public void testCreatePrivateProfileOnWatch_shouldNotAllowCreation() {
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_WATCH), anyInt());
int mainUser = mUms.getMainUserId();
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index aec896f..f86ff14 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -64,6 +64,7 @@
import android.content.PermissionChecker;
import android.content.res.Resources;
import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
import android.hardware.display.AmbientDisplayConfiguration;
@@ -154,6 +155,8 @@
private static final float BRIGHTNESS_FACTOR = 0.7f;
private static final boolean BATTERY_SAVER_ENABLED = true;
+ private static final DeviceState DEVICE_STATE_1 = new DeviceState(
+ new DeviceState.Configuration.Builder(1 /* identifier */, "" /* name */).build());
@Mock private BatterySaverController mBatterySaverControllerMock;
@Mock private BatterySaverPolicy mBatterySaverPolicyMock;
@@ -2839,7 +2842,7 @@
// Send a display state change event and advance the clock 10.
final DeviceStateCallback deviceStateCallback = deviceStateCallbackCaptor.getValue();
- deviceStateCallback.onStateChanged(1);
+ deviceStateCallback.onDeviceStateChanged(DEVICE_STATE_1);
final long timeToAdvance = 10;
advanceTime(timeToAdvance);
@@ -2849,7 +2852,7 @@
assertThat(mService.wasDeviceIdleForInternal(timeToAdvance)).isFalse();
// Send the same state and ensure that does not trigger an update.
- deviceStateCallback.onStateChanged(1);
+ deviceStateCallback.onDeviceStateChanged(DEVICE_STATE_1);
advanceTime(timeToAdvance);
final long newTime = timeToAdvance * 2;
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 0089d4c..2724149 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -111,6 +111,8 @@
<uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" />
<uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
<uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.CAMERA" />
<queries>
<package android:name="com.android.servicestests.apps.suspendtestapp" />
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 7f88b00..b2ecea1 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -88,6 +88,7 @@
import com.android.compatibility.common.util.TestUtils;
import com.android.internal.R;
+import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.FloatingMenuSize;
import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
@@ -812,7 +813,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DISABLE_CONTINUOUS_SHORTCUT_ON_FORCE_STOP)
public void testPackagesForceStopped_fromContinuousService_removesButtonTarget() {
final AccessibilityServiceInfo info_a = new AccessibilityServiceInfo();
info_a.setComponentName(COMPONENT_NAME);
@@ -1318,6 +1318,152 @@
}
}
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void notifyQuickSettingsTilesChanged_statusBarServiceNotGranted_throwsException() {
+ mTestableContext.getTestablePermissions().setPermission(
+ Manifest.permission.STATUS_BAR_SERVICE, PackageManager.PERMISSION_DENIED);
+ mockManageAccessibilityGranted(mTestableContext);
+
+ assertThrows(SecurityException.class,
+ () -> mA11yms.notifyQuickSettingsTilesChanged(
+ mA11yms.getCurrentUserState().mUserId,
+ List.of(
+ AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME)));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void notifyQuickSettingsTilesChanged_manageAccessibilityNotGranted_throwsException() {
+ mockStatusBarServiceGranted(mTestableContext);
+ mTestableContext.getTestablePermissions().setPermission(
+ Manifest.permission.STATUS_BAR_SERVICE, PackageManager.PERMISSION_DENIED);
+
+ assertThrows(SecurityException.class,
+ () -> mA11yms.notifyQuickSettingsTilesChanged(
+ mA11yms.getCurrentUserState().mUserId,
+ List.of(
+ AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME)));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void notifyQuickSettingsTilesChanged_qsTileChanges_updateA11yTilesInQsPanel() {
+ mockStatusBarServiceGranted(mTestableContext);
+ mockManageAccessibilityGranted(mTestableContext);
+ List<ComponentName> tiles = List.of(
+ AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME,
+ AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME
+ );
+
+ mA11yms.notifyQuickSettingsTilesChanged(
+ mA11yms.getCurrentUserState().mUserId,
+ tiles
+ );
+
+ assertThat(
+ mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel()
+ ).containsExactlyElementsIn(tiles);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void notifyQuickSettingsTilesChanged_sameQsTiles_noUpdateToA11yTilesInQsPanel() {
+ notifyQuickSettingsTilesChanged_qsTileChanges_updateA11yTilesInQsPanel();
+ List<ComponentName> tiles =
+ mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel().stream().toList();
+
+ mA11yms.notifyQuickSettingsTilesChanged(
+ mA11yms.getCurrentUserState().mUserId,
+ tiles
+ );
+
+ assertThat(
+ mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel()
+ ).containsExactlyElementsIn(tiles);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void notifyQuickSettingsTilesChanged_serviceWarningRequired_qsShortcutRemainDisabled() {
+ mockStatusBarServiceGranted(mTestableContext);
+ mockManageAccessibilityGranted(mTestableContext);
+ setupShortcutTargetServices();
+ ComponentName tile = new ComponentName(
+ TARGET_ALWAYS_ON_A11Y_SERVICE.getPackageName(),
+ TARGET_ALWAYS_ON_A11Y_SERVICE_TILE_CLASS);
+
+ mA11yms.notifyQuickSettingsTilesChanged(
+ mA11yms.getCurrentUserState().mUserId,
+ List.of(tile)
+ );
+
+ assertThat(mA11yms.getCurrentUserState().getA11yQsTargets()).doesNotContain(tile);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void notifyQuickSettingsTilesChanged_serviceWarningNotRequired_qsShortcutEnabled() {
+ mockStatusBarServiceGranted(mTestableContext);
+ mockManageAccessibilityGranted(mTestableContext);
+ setupShortcutTargetServices();
+ final AccessibilityUserState userState = mA11yms.getCurrentUserState();
+ userState.mAccessibilityButtonTargets.clear();
+ userState.mAccessibilityButtonTargets.add(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString());
+ ComponentName tile = new ComponentName(
+ TARGET_ALWAYS_ON_A11Y_SERVICE.getPackageName(),
+ TARGET_ALWAYS_ON_A11Y_SERVICE_TILE_CLASS);
+
+ mA11yms.notifyQuickSettingsTilesChanged(
+ mA11yms.getCurrentUserState().mUserId,
+ List.of(tile)
+ );
+
+ assertThat(mA11yms.getCurrentUserState().getA11yQsTargets())
+ .contains(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void notifyQuickSettingsTilesChanged_addFrameworkTile_qsShortcutEnabled() {
+ mockStatusBarServiceGranted(mTestableContext);
+ mockManageAccessibilityGranted(mTestableContext);
+ List<ComponentName> tiles = List.of(
+ AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME,
+ AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME
+ );
+
+ mA11yms.notifyQuickSettingsTilesChanged(
+ mA11yms.getCurrentUserState().mUserId,
+ tiles
+ );
+
+ assertThat(
+ mA11yms.getCurrentUserState().getA11yQsTargets()
+ ).containsExactlyElementsIn(List.of(
+ AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString(),
+ AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString())
+ );
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void notifyQuickSettingsTilesChanged_removeFrameworkTile_qsShortcutDisabled() {
+ notifyQuickSettingsTilesChanged_addFrameworkTile_qsShortcutEnabled();
+ Set<ComponentName> qsTiles = mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel();
+ qsTiles.remove(AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME);
+
+ mA11yms.notifyQuickSettingsTilesChanged(
+ mA11yms.getCurrentUserState().mUserId,
+ qsTiles.stream().toList()
+ );
+
+ assertThat(
+ mA11yms.getCurrentUserState().getA11yQsTargets()
+ ).doesNotContain(
+ AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString());
+ }
+
private static AccessibilityServiceInfo mockAccessibilityServiceInfo(
ComponentName componentName) {
return mockAccessibilityServiceInfo(
@@ -1367,6 +1513,11 @@
PackageManager.PERMISSION_GRANTED);
}
+ private void mockStatusBarServiceGranted(TestableContext context) {
+ context.getTestablePermissions().setPermission(Manifest.permission.STATUS_BAR_SERVICE,
+ PackageManager.PERMISSION_GRANTED);
+ }
+
private void assertStartActivityWithExpectedComponentName(Context mockContext,
String componentName) {
verify(mockContext).startActivityAsUser(mIntentArgumentCaptor.capture(),
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index f86cb7b..cda8b01 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -208,7 +208,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ADD_WINDOW_TOKEN_WITHOUT_LOCK)
public void onServiceConnected_addsWindowTokens() {
setServiceBinding(COMPONENT_NAME);
mConnection.bindLocked();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index 52a5d8f..b269beb9 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -30,6 +30,8 @@
import static com.android.server.accessibility.AccessibilityUserState.doesShortcutTargetsStringContain;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
@@ -45,9 +47,10 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.graphics.Color;
-import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
@@ -59,6 +62,7 @@
import androidx.test.InstrumentationRegistry;
import com.android.internal.R;
+import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.internal.util.test.FakeSettingsProvider;
import org.junit.After;
@@ -68,6 +72,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Map;
+import java.util.Set;
+
/** Tests for AccessibilityUserState */
public class AccessibilityUserStateTest {
@@ -212,18 +219,6 @@
}
@Test
- // addServiceLocked only calls addWindowTokensForAllDisplays when
- // FLAG_ADD_WINDOW_TOKEN_WITHOUT_LOCK is off, so skip the test if it is on.
- @RequiresFlagsDisabled(Flags.FLAG_ADD_WINDOW_TOKEN_WITHOUT_LOCK)
- public void addService_flagDisabled_addsWindowTokens() {
- when(mMockConnection.getComponentName()).thenReturn(COMPONENT_NAME);
-
- mUserState.addServiceLocked(mMockConnection);
-
- verify(mMockConnection).addWindowTokensForAllDisplays();
- }
-
- @Test
public void reconcileSoftKeyboardMode_whenStateNotMatchSettings_setBothDefault() {
// When soft kb show mode is hidden in settings and is auto in state.
putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
@@ -431,7 +426,70 @@
assertEquals(focusStrokeWidthValue, mUserState.getFocusStrokeWidthLocked());
assertEquals(focusColorValue, mUserState.getFocusColorLocked());
+ }
+ @Test
+ public void updateA11yQsTargetLocked_valueUpdated() {
+ Set<String> newTargets = Set.of(
+ AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString(),
+ AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString()
+ );
+
+ mUserState.updateA11yQsTargetLocked(newTargets);
+
+ assertThat(mUserState.getA11yQsTargets()).isEqualTo(newTargets);
+ }
+
+ @Test
+ public void getA11yQsTargets_returnsCopiedData() {
+ updateA11yQsTargetLocked_valueUpdated();
+
+ Set<String> targets = mUserState.getA11yQsTargets();
+ targets.clear();
+
+ assertThat(mUserState.getA11yQsTargets()).isNotEmpty();
+ }
+
+ @Test
+ public void updateA11yTilesInQsPanelLocked_valueUpdated() {
+ Set<ComponentName> newTargets = Set.of(
+ AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME,
+ AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME
+ );
+
+ mUserState.updateA11yTilesInQsPanelLocked(newTargets);
+
+ assertThat(mUserState.getA11yQsTilesInQsPanel()).isEqualTo(newTargets);
+ }
+
+ @Test
+ public void getA11yQsTilesInQsPanel_returnsCopiedData() {
+ updateA11yTilesInQsPanelLocked_valueUpdated();
+
+ Set<ComponentName> targets = mUserState.getA11yQsTilesInQsPanel();
+ targets.clear();
+
+ assertThat(mUserState.getA11yQsTilesInQsPanel()).isNotEmpty();
+ }
+
+ @Test
+ public void getTileServiceToA11yServiceInfoMapLocked() {
+ final ComponentName tileComponent =
+ new ComponentName(COMPONENT_NAME.getPackageName(), "FakeTileService");
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = tileComponent.getPackageName();
+ serviceInfo.name = COMPONENT_NAME.getClassName();
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.serviceInfo = serviceInfo;
+ when(mMockServiceInfo.getTileServiceName()).thenReturn(tileComponent.getClassName());
+ when(mMockServiceInfo.getResolveInfo()).thenReturn(resolveInfo);
+ mUserState.mInstalledServices.add(mMockServiceInfo);
+ mUserState.updateTileServiceMapForAccessibilityServiceLocked();
+
+ Map<ComponentName, AccessibilityServiceInfo> actual =
+ mUserState.getTileServiceToA11yServiceInfoMapLocked();
+
+ assertThat(actual).containsExactly(tileComponent, mMockServiceInfo);
}
private int getSecureIntForUser(String key, int userId) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
index dc26e6e..f6dc2f0 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
@@ -18,21 +18,23 @@
import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_DEFAULT;
import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.DisplayIdMatcher.displayId;
+import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowIdMatcher.windowId;
import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowChangesMatcher.a11yWindowChanges;
-import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowIdMatcher.a11yWindowId;
+import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.EventWindowIdMatcher.eventWindowId;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
@@ -350,6 +352,88 @@
}
@Test
+ public void onWindowsChanged_shouldNotReportNonTouchableWindow() {
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ when(window.isTouchable()).thenReturn(false);
+ final int windowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, window.getWindowInfo().token);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, not(hasItem(windowId(windowId))));
+ }
+
+ @Test
+ public void onWindowsChanged_shouldReportFocusedNonTouchableWindow() {
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX);
+ when(window.isTouchable()).thenReturn(false);
+ final int windowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, window.getWindowInfo().token);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasItem(windowId(windowId)));
+ }
+
+ @Test
+ public void onWindowsChanged_trustedFocusedNonTouchableWindow_shouldNotHideWindowsBelow() {
+ // Make the focused trusted un-touchable window fullscreen.
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX);
+ setRegionForMockAccessibilityWindow(window, new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+ when(window.isTouchable()).thenReturn(false);
+ when(window.isTrustedOverlay()).thenReturn(true);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(NUM_OF_WINDOWS));
+ }
+
+ @Test
+ public void onWindowsChanged_accessibilityOverlay_shouldNotHideWindowsBelow() {
+ // Make the a11y overlay window fullscreen.
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(window, new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+ when(window.getType()).thenReturn(WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(NUM_OF_WINDOWS));
+ }
+
+ @Test
+ public void onWindowsChanged_shouldReportFocusedWindowEvenIfOccluded() {
+ // Make the front window fullscreen.
+ final AccessibilityWindow frontWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(frontWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+ final int frontWindowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, frontWindow.getWindowInfo().token);
+
+ final AccessibilityWindow focusedWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX);
+ final int focusedWindowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, focusedWindow.getWindowInfo().token);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(2));
+ assertThat(a11yWindows.get(0), windowId(frontWindowId));
+ assertThat(a11yWindows.get(1), windowId(focusedWindowId));
+ }
+
+ @Test
public void onWindowsChangedAndForceSend_shouldUpdateWindows() {
assertNotEquals("new title",
toString(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)
@@ -631,11 +715,11 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(currentActiveWindowId),
+ eventWindowId(currentActiveWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
assertThat(captor.getAllValues().get(1),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
}
@@ -661,7 +745,7 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(
AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
}
@@ -710,12 +794,12 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(initialDisplayId),
- a11yWindowId(initialWindowId),
+ eventWindowId(initialWindowId),
a11yWindowChanges(
AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
assertThat(captor.getAllValues().get(1),
allOf(displayId(eventDisplayId),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(
AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
}
@@ -771,11 +855,11 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
assertThat(captor.getAllValues().get(1),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(currentActiveWindowId),
+ eventWindowId(currentActiveWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
}
@@ -979,7 +1063,7 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(windowId),
+ eventWindowId(windowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_REMOVED)));
}
@@ -1001,7 +1085,7 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(windowId),
+ eventWindowId(windowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ADDED)));
}
@@ -1019,7 +1103,7 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(windowId),
+ eventWindowId(windowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_TITLE)));
}
@@ -1173,8 +1257,6 @@
private AccessibilityWindow createMockAccessibilityWindow(IWindow windowToken, int displayId) {
final WindowInfo windowInfo = WindowInfo.obtain();
- // TODO(b/325341171): add tests with various kinds of windows such as
- // changing window types, touchable or not, trusted or not, etc.
windowInfo.type = WindowManager.LayoutParams.TYPE_APPLICATION;
windowInfo.token = windowToken.asBinder();
@@ -1235,16 +1317,16 @@
}
}
- static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
+ static class EventWindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
private int mWindowId;
- WindowIdMatcher(int windowId) {
+ EventWindowIdMatcher(int windowId) {
super();
mWindowId = windowId;
}
- static WindowIdMatcher a11yWindowId(int windowId) {
- return new WindowIdMatcher(windowId);
+ static EventWindowIdMatcher eventWindowId(int windowId) {
+ return new EventWindowIdMatcher(windowId);
}
@Override
@@ -1280,4 +1362,27 @@
description.appendText("Matching to window changes " + mWindowChanges);
}
}
+
+ static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityWindowInfo> {
+ private final int mWindowId;
+
+ WindowIdMatcher(int windowId) {
+ super();
+ mWindowId = windowId;
+ }
+
+ static WindowIdMatcher windowId(int windowId) {
+ return new WindowIdMatcher(windowId);
+ }
+
+ @Override
+ protected boolean matchesSafely(AccessibilityWindowInfo window) {
+ return window.getId() == mWindowId;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Matching to windowId " + mWindowId);
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
index 3ee5f61..95a1f5a 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
@@ -39,7 +39,6 @@
import android.content.pm.ServiceInfo;
import android.hardware.display.DisplayManager;
import android.os.IBinder;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.view.WindowManager;
@@ -209,7 +208,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ADD_WINDOW_TOKEN_WITHOUT_LOCK)
public void registerUiAutomationService_callsAddWindowTokenUsingHandler() {
register(0);
// registerUiTestAutomationServiceLocked() should not directly call addWindowToken.
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
index 2890078..8a6ba4d 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
@@ -67,7 +67,6 @@
// Start some ops
appOpsManager.startOp(AppOpsManager.OP_FINE_LOCATION);
appOpsManager.startOp(AppOpsManager.OP_CAMERA);
- appOpsManager.startOp(AppOpsManager.OP_RECORD_AUDIO);
// Verify that we got called for the ops being started
final InOrder inOrder = inOrder(listener);
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 67b131f..1249707 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -206,7 +206,6 @@
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.MethodRule;
@@ -2151,14 +2150,12 @@
assertFalse(mService.isUidNetworkingBlocked(UID_E, false));
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testBackgroundChainEnabled() throws Exception {
verify(mNetworkManager).setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true);
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testBackgroundChainOnProcStateChange() throws Exception {
@@ -2188,7 +2185,6 @@
assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testBackgroundChainOnAllowlistChange() throws Exception {
@@ -2227,7 +2223,6 @@
assertFalse(mService.isUidNetworkingBlocked(UID_B, false));
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testBackgroundChainOnTempAllowlistChange() throws Exception {
@@ -2266,7 +2261,6 @@
&& uidState.procState == procState && uidState.capability == capability;
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testUidObserverFiltersProcStateChanges() throws Exception {
@@ -2329,7 +2323,6 @@
waitForUidEventHandlerIdle();
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testUidObserverFiltersStaleChanges() throws Exception {
@@ -2350,7 +2343,6 @@
waitForUidEventHandlerIdle();
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testUidObserverFiltersCapabilityChanges() throws Exception {
@@ -2430,7 +2422,6 @@
assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testObsoleteHandleUidChanged() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
index e249cd7..55c48e0 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
@@ -90,8 +90,6 @@
public void testCanDeviceOwnerChange() {
assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_RECORD_AUDIO));
assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_WALLPAPER));
- assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(
- UserManager.DISALLOW_ADD_PRIVATE_PROFILE));
assertTrue(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_ADD_USER));
assertTrue(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_USER_SWITCH));
}
@@ -110,10 +108,6 @@
UserManager.DISALLOW_USER_SWITCH,
true,
false));
- assertFalse(UserRestrictionsUtils.canProfileOwnerChange(
- UserManager.DISALLOW_ADD_PRIVATE_PROFILE,
- true,
- false));
assertTrue(UserRestrictionsUtils.canProfileOwnerChange(
UserManager.DISALLOW_ADD_USER,
true,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index f29d215..99ab405 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -325,7 +325,6 @@
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
-import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -13226,35 +13225,6 @@
}
@Test
- public void fixNotification_customAllowlistToken()
- throws Exception {
- Notification n = new Notification.Builder(mContext, "test")
- .build();
- try {
- Field allowlistToken = Class.forName("android.app.Notification").
- getDeclaredField("mAllowlistToken");
- allowlistToken.setAccessible(true);
- allowlistToken.set(n, new Binder());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
-
- mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
-
- IBinder actual = null;
- try {
- Field allowlistToken = Class.forName("android.app.Notification").
- getDeclaredField("mAllowlistToken");
- allowlistToken.setAccessible(true);
- actual = (IBinder) allowlistToken.get(n);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
-
- assertTrue(mService.ALLOWLIST_TOKEN == actual);
- }
-
- @Test
public void testCancelAllNotifications_IgnoreUserInitiatedJob() throws Exception {
when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
.thenReturn(true);
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index fee6582..dff4984 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -43,9 +43,12 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER;
+import static org.mockito.ArgumentMatchers.eq;
+
import static java.util.Collections.unmodifiableMap;
import android.content.Context;
+import android.content.res.Resources;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArrayMap;
import android.view.InputDevice;
@@ -57,6 +60,7 @@
import com.android.internal.util.test.FakeSettingsProviderRule;
import org.junit.After;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.RuleChain;
@@ -70,9 +74,10 @@
@Rule
public RuleChain rules = RuleChain.outerRule(mSettingsProviderRule).around(mSetFlagsRule);
+ private Resources mResources;
TestPhoneWindowManager mPhoneWindowManager;
DispatchedKeyHandler mDispatchedKeyHandler = event -> false;
- final Context mContext = spy(getInstrumentation().getTargetContext());
+ Context mContext;
/** Modifier key to meta state */
protected static final Map<Integer, Integer> MODIFIER;
@@ -90,6 +95,16 @@
MODIFIER = unmodifiableMap(map);
}
+ @Before
+ public void setup() {
+ mContext = spy(getInstrumentation().getTargetContext());
+ mResources = spy(mContext.getResources());
+ doReturn(mResources).when(mContext).getResources();
+ doReturn(mSettingsProviderRule.mockContentResolver(mContext))
+ .when(mContext).getContentResolver();
+ }
+
+
/** Same as {@link setUpPhoneWindowManager(boolean)}, without supporting settings update. */
protected final void setUpPhoneWindowManager() {
setUpPhoneWindowManager(/* supportSettingsUpdate= */ false);
@@ -101,12 +116,14 @@
* <p>Subclasses must call this at the start of the test if they intend to interact with phone
* window manager.
*
- * @param supportSettingsUpdate {@code true} if this test should read and listen to provider
- * settings values.
+ * @param supportSettingsUpdate {@code true} to have PWM respond to any Settings changes upon
+ * instantiation. Although this is supposed to also allow a test to listen to any Settings
+ * changes after instantiation, MockContentResolver in this class's setup stubs out
+ * notifyChange(), which prevents SettingsObserver from getting notified of events. So
+ * we're effectively always instantiating TestPhoneWindowManager with
+ * supportSettingsUpdate=false.
*/
protected final void setUpPhoneWindowManager(boolean supportSettingsUpdate) {
- doReturn(mSettingsProviderRule.mockContentResolver(mContext))
- .when(mContext).getContentResolver();
mPhoneWindowManager = new TestPhoneWindowManager(mContext, supportSettingsUpdate);
}
@@ -187,6 +204,23 @@
sendKeyCombination(new int[]{keyCode}, 0 /*durationMillis*/, longPress, DEFAULT_DISPLAY);
}
+ /**
+ * Since we use SettingsProviderRule to mock the ContentResolver in these
+ * tests, the settings observer registered by PhoneWindowManager will not
+ * be triggered automatically by the mock. Use this method to force the
+ * settings observer change after modifying any settings.
+ */
+ void triggerSettingsObserverChange() {
+ mPhoneWindowManager.getSettingsObserver().onChange(
+ // This boolean doesn't matter. This observer does the same thing regardless.
+ /*selfChange=*/true);
+ }
+
+ /** Override a resource's return value. */
+ void overrideResource(int resId, int expectedBehavior) {
+ doReturn(expectedBehavior).when(mResources).getInteger(eq(resId));
+ }
+
private void interceptKey(KeyEvent keyEvent) {
int actions = mPhoneWindowManager.interceptKeyBeforeQueueing(keyEvent);
if ((actions & ACTION_PASS_TO_USER) != 0) {
diff --git a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
index 77e7a0a..2e85025 100644
--- a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
@@ -291,6 +291,101 @@
mPhoneWindowManager.assertSwitchToTask(referenceId);
}
+ /**
+ * Ensure the stem rule is added even when button behaviors are set to nothing.
+ *
+ * This makes sure that if stem key behaviors are overridden to NOTHING, then we check the
+ * XML config as the source of truth upon reboot to see whether a device should have a stem
+ * key rule. This test walks us through a scenario where a device powers off during Wear's
+ * Touch Lock mode.
+ */
+ @Test
+ public void stemKeyRuleIsAddedEvenWhenBehaviorsRemoved() {
+ // deactivate stem button presses
+ overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS,
+ PhoneWindowManager.SHORT_PRESS_PRIMARY_NOTHING);
+ overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS,
+ PhoneWindowManager.DOUBLE_PRESS_PRIMARY_NOTHING);
+ overrideBehavior(STEM_PRIMARY_BUTTON_TRIPLE_PRESS,
+ PhoneWindowManager.TRIPLE_PRESS_PRIMARY_NOTHING);
+ overrideBehavior(STEM_PRIMARY_BUTTON_LONG_PRESS,
+ PhoneWindowManager.LONG_PRESS_PRIMARY_NOTHING);
+
+ // pretend like we have stem keys enabled in the xmls
+ overrideResource(
+ com.android.internal.R.integer.config_shortPressOnStemPrimaryBehavior,
+ SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
+
+ // start the PhoneWindowManager, just like would happen with a reboot
+ setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+ // Set the stem behavior back to something normal after boot
+ overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS,
+ SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
+ // manually trigger the SettingsObserver's onChange() method because subclasses of
+ // ShortcutKeyTestBase cannot automatically pick up Settings changes.
+ triggerSettingsObserverChange();
+
+ // These calls are required to make the All Apps view show up
+ mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
+ mPhoneWindowManager.overrideStartActivity();
+ mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
+ mPhoneWindowManager.overrideIsUserSetupComplete(true);
+
+ sendKey(KEYCODE_STEM_PRIMARY);
+
+ // Because the rule was loaded and we changed the behavior back to non-zero, PWM should
+ // actually perform this action. It would not perform the action if the rule was missing.
+ mPhoneWindowManager.assertOpenAllAppView();
+ }
+
+ /**
+ * Ensure the stem rule is not added when stem behavior is not defined in the xml.
+ *
+ * This is the opposite of the test above.
+ */
+ @Test
+ public void stemKeyRuleIsNotAddedWhenXmlDoesntDefineIt() {
+ // deactivate stem button presses
+ overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS,
+ PhoneWindowManager.SHORT_PRESS_PRIMARY_NOTHING);
+ overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS,
+ PhoneWindowManager.DOUBLE_PRESS_PRIMARY_NOTHING);
+ overrideBehavior(STEM_PRIMARY_BUTTON_TRIPLE_PRESS,
+ PhoneWindowManager.TRIPLE_PRESS_PRIMARY_NOTHING);
+ overrideBehavior(STEM_PRIMARY_BUTTON_LONG_PRESS,
+ PhoneWindowManager.LONG_PRESS_PRIMARY_NOTHING);
+
+ // pretend like we do not have stem keys enabled in the xmls
+ overrideResource(
+ com.android.internal.R.integer.config_shortPressOnStemPrimaryBehavior,
+ PhoneWindowManager.SHORT_PRESS_PRIMARY_NOTHING);
+ overrideResource(
+ com.android.internal.R.integer.config_longPressOnStemPrimaryBehavior,
+ PhoneWindowManager.LONG_PRESS_PRIMARY_NOTHING);
+
+ // start the PhoneWindowManager, just like would happen with a reboot
+ setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+ // Set the stem behavior back to something normal after boot
+ // (Despite this fact, a stem press shouldn't have any behavior because there's no rule.)
+ overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS,
+ SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
+ // manually trigger the SettingsObserver's onChange() method because subclasses of
+ // ShortcutKeyTestBase cannot automatically pick up Settings changes.
+ triggerSettingsObserverChange();
+
+ // These calls are required to make the All Apps view show up
+ mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
+ mPhoneWindowManager.overrideStartActivity();
+ mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
+ mPhoneWindowManager.overrideIsUserSetupComplete(true);
+
+ sendKey(KEYCODE_STEM_PRIMARY);
+
+ // Because the rule was not loaded, PWM should not actually perform this action, even
+ // though the Settings override is set to non-null.
+ mPhoneWindowManager.assertNotOpenAllAppView();
+ }
+
private void overrideBehavior(String key, int expectedBehavior) {
Settings.Global.putLong(mContext.getContentResolver(), key, expectedBehavior);
}
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 0776c51..52df010 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -66,6 +66,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.database.ContentObserver;
import android.hardware.SensorPrivacyManager;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
@@ -246,6 +247,13 @@
}
}
+ /**
+ * {@link TestPhoneWindowManager}'s constructor.
+ *
+ * @param context The {@Context} to be used in any Context-related actions.
+ * @param supportSettingsUpdate {@code true} if this object should read and listen to provider
+ * settings values.
+ */
TestPhoneWindowManager(Context context, boolean supportSettingsUpdate) {
MockitoAnnotations.initMocks(this);
mHandler = new Handler(mTestLooper.getLooper());
@@ -416,6 +424,13 @@
mPhoneWindowManager.dispatchUnhandledKey(mInputToken, event, FLAG_INTERACTIVE);
}
+ /**
+ * Provide access to the SettingsObserver so that tests can manually trigger Settings changes.
+ */
+ ContentObserver getSettingsObserver() {
+ return mPhoneWindowManager.mSettingsObserver;
+ }
+
long getCurrentTime() {
return mClock.now();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 961fdfb..3bd6496 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -315,6 +315,23 @@
}
@Test
+ public void testUserLeaving() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final Task task = activity.getTask();
+ mSupervisor.mUserLeaving = true;
+ activity.setState(ActivityRecord.State.RESUMED, "test");
+ task.sleepIfPossible(false /* shuttingDown */);
+ verify(task).startPausing(eq(true) /* userLeaving */, anyBoolean(), any(), any());
+
+ clearInvocations(task);
+ activity.setState(ActivityRecord.State.RESUMED, "test");
+ task.setPausingActivity(null);
+ doReturn(false).when(task).canBeResumed(any());
+ task.pauseActivityIfNeeded(null /* resuming */, "test");
+ verify(task).startPausing(eq(true) /* userLeaving */, anyBoolean(), any(), any());
+ }
+
+ @Test
public void testSwitchUser() {
final Task rootTask = createTask(mDisplayContent);
final Task childTask = createTaskInRootTask(rootTask, 0 /* userId */);
@@ -606,6 +623,23 @@
doReturn(true).when(root).fillsParent();
}
+ @Test
+ public void testIsTopActivityTranslucent() {
+ DisplayContent display = mAtm.mRootWindowContainer.getDefaultDisplay();
+ final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
+ final Task task = rootTask.getBottomMostTask();
+ final ActivityRecord root = task.getTopNonFinishingActivity();
+ spyOn(mWm.mLetterboxConfiguration);
+ spyOn(root);
+
+ doReturn(false).when(root).fillsParent();
+ assertTrue(task.getTaskInfo().isTopActivityTransparent);
+
+ doReturn(true).when(root).fillsParent();
+ assertFalse(task.getTaskInfo().isTopActivityTransparent);
+ }
+
/**
* Tests that a task with forced orientation has orientation-consistent bounds within the
* parent.
@@ -1823,6 +1857,66 @@
}
@Test
+ public void testAssignChildLayers_boostedDecorSurfacePlacement() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
+ final ActivityRecord unembeddedActivity = task.getTopMostActivity();
+
+ final TaskFragment fragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final TaskFragment fragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final SurfaceControl.Transaction t = task.getSyncTransaction();
+ final SurfaceControl.Transaction clientTransaction = mock(SurfaceControl.Transaction.class);
+
+ doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded();
+ spyOn(unembeddedActivity);
+ spyOn(fragment1);
+ spyOn(fragment2);
+
+ doReturn(true).when(unembeddedActivity).isUid(task.effectiveUid);
+ doReturn(true).when(fragment1).isAllowedToBeEmbeddedInTrustedMode();
+ doReturn(false).when(fragment2).isAllowedToBeEmbeddedInTrustedMode();
+ doReturn(true).when(fragment1).isVisible();
+
+ task.moveOrCreateDecorSurfaceFor(fragment1);
+
+ clearInvocations(t);
+ clearInvocations(unembeddedActivity);
+ clearInvocations(fragment1);
+ clearInvocations(fragment2);
+
+ // The decor surface should be placed above all the windows when boosted and the cover
+ // surface should show.
+ task.setDecorSurfaceBoosted(fragment1, true /* isBoosted */, clientTransaction);
+
+ verify(unembeddedActivity).assignLayer(t, 0);
+ verify(fragment1).assignLayer(t, 1);
+ verify(fragment2).assignLayer(t, 2);
+ verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 3);
+
+ verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, true);
+ verify(t).merge(clientTransaction);
+
+ clearInvocations(t);
+ clearInvocations(unembeddedActivity);
+ clearInvocations(fragment1);
+ clearInvocations(fragment2);
+
+ // The decor surface should be placed just above the owner TaskFragment and the cover
+ // surface should hide.
+ task.moveOrCreateDecorSurfaceFor(fragment1);
+ task.setDecorSurfaceBoosted(fragment1, false /* isBoosted */, clientTransaction);
+
+ verify(unembeddedActivity).assignLayer(t, 0);
+ verify(fragment1).assignLayer(t, 1);
+ verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 2);
+ verify(fragment2).assignLayer(t, 3);
+
+ verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, true);
+ verify(t).merge(clientTransaction);
+
+ }
+
+ @Test
public void testMoveTaskFragmentsToBottomIfNeeded() {
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 03b695d..43b424f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1376,8 +1376,9 @@
assertTrue(w1.syncNextBuffer());
assertTrue(w2.syncNextBuffer());
- // A drawn window can complete the sync state automatically.
+ // A drawn window in non-explicit sync can complete the sync state automatically.
w1.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
+ w1.mPrepareSyncSeqId = 0;
makeLastConfigReportedToClient(w1, true /* visible */);
mWm.mSyncEngine.onSurfacePlacement();
verify(mockCallback).onTransactionReady(anyInt(), any());
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 77b2638..9acda5f 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -142,8 +142,6 @@
"/sys/class/android_usb/android0/state";
private static final String RNDIS_ETH_ADDR_PATH =
"/sys/class/android_usb/android0/f_rndis/ethaddr";
- private static final String AUDIO_SOURCE_PCM_PATH =
- "/sys/class/android_usb/android0/f_audio_source/pcm";
private static final String MIDI_ALSA_PATH =
"/sys/class/android_usb/android0/f_midi/alsa";
@@ -172,8 +170,6 @@
private static final int MSG_UPDATE_USB_SPEED = 22;
private static final int MSG_UPDATE_HAL_VERSION = 23;
- private static final int AUDIO_MODE_SOURCE = 1;
-
// Delay for debouncing USB disconnects.
// We often get rapid connect/disconnect events when enabling USB functions,
// which need debouncing.
@@ -464,7 +460,6 @@
int operationId = sUsbOperationCount.incrementAndGet();
mAccessoryStrings = nativeGetAccessoryStrings();
- boolean enableAudio = (nativeGetAudioMode() == AUDIO_MODE_SOURCE);
// don't start accessory mode if our mandatory strings have not been set
boolean enableAccessory = (mAccessoryStrings != null &&
mAccessoryStrings[UsbAccessory.MANUFACTURER_STRING] != null &&
@@ -474,9 +469,6 @@
if (enableAccessory) {
functions |= UsbManager.FUNCTION_ACCESSORY;
}
- if (enableAudio) {
- functions |= UsbManager.FUNCTION_AUDIO_SOURCE;
- }
if (functions != UsbManager.FUNCTION_NONE) {
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_ACCESSORY_MODE_ENTER_TIMEOUT),
@@ -2490,6 +2482,4 @@
private native FileDescriptor nativeOpenControl(String usbFunction);
private native boolean nativeIsStartRequested();
-
- private native int nativeGetAudioMode();
}
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index 5ba5ee883..8b9a93b 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -16,6 +16,8 @@
package android.telecom;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -28,8 +30,11 @@
import android.telephony.ims.ImsReasonInfo;
import android.text.TextUtils;
+import androidx.annotation.IntDef;
+
import com.android.server.telecom.flags.Flags;
+import java.lang.annotation.Retention;
import java.util.Objects;
/**
@@ -42,48 +47,69 @@
public final class DisconnectCause implements Parcelable {
/** Disconnected because of an unknown or unspecified reason. */
- public static final int UNKNOWN = TelecomProtoEnums.UNKNOWN; // = 0
+ public static final int UNKNOWN = 0;
/** Disconnected because there was an error, such as a problem with the network. */
- public static final int ERROR = TelecomProtoEnums.ERROR; // = 1
+ public static final int ERROR = 1;
/** Disconnected because of a local user-initiated action, such as hanging up. */
- public static final int LOCAL = TelecomProtoEnums.LOCAL; // = 2
+ public static final int LOCAL = 2;
/**
* Disconnected because the remote party hung up an ongoing call, or because an outgoing call
* was not answered by the remote party.
*/
- public static final int REMOTE = TelecomProtoEnums.REMOTE; // = 3
+ public static final int REMOTE = 3;
/** Disconnected because it has been canceled. */
- public static final int CANCELED = TelecomProtoEnums.CANCELED; // = 4
+ public static final int CANCELED = 4;
/** Disconnected because there was no response to an incoming call. */
- public static final int MISSED = TelecomProtoEnums.MISSED; // = 5
+ public static final int MISSED = 5;
/** Disconnected because the user rejected an incoming call. */
- public static final int REJECTED = TelecomProtoEnums.REJECTED; // = 6
+ public static final int REJECTED = 6;
/** Disconnected because the other party was busy. */
- public static final int BUSY = TelecomProtoEnums.BUSY; // = 7
+ public static final int BUSY = 7;
/**
* Disconnected because of a restriction on placing the call, such as dialing in airplane
* mode.
*/
- public static final int RESTRICTED = TelecomProtoEnums.RESTRICTED; // = 8
+ public static final int RESTRICTED = 8;
/** Disconnected for reason not described by other disconnect codes. */
- public static final int OTHER = TelecomProtoEnums.OTHER; // = 9
+ public static final int OTHER = 9;
/**
* Disconnected because the connection manager did not support the call. The call will be tried
* again without a connection manager. See {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}.
*/
- public static final int CONNECTION_MANAGER_NOT_SUPPORTED =
- TelecomProtoEnums.CONNECTION_MANAGER_NOT_SUPPORTED; // = 10
+ public static final int CONNECTION_MANAGER_NOT_SUPPORTED = 10;
/**
* Disconnected because the user did not locally answer the incoming call, but it was answered
* on another device where the call was ringing.
*/
- public static final int ANSWERED_ELSEWHERE = TelecomProtoEnums.ANSWERED_ELSEWHERE; // = 11
+ public static final int ANSWERED_ELSEWHERE = 11;
/**
* Disconnected because the call was pulled from the current device to another device.
*/
- public static final int CALL_PULLED = TelecomProtoEnums.CALL_PULLED; // = 12
+ public static final int CALL_PULLED = 12;
+
+ /**
+ * @hide
+ */
+ @Retention(SOURCE)
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ @IntDef({
+ UNKNOWN,
+ ERROR,
+ LOCAL,
+ REMOTE,
+ CANCELED,
+ MISSED,
+ REJECTED,
+ BUSY,
+ RESTRICTED,
+ OTHER,
+ CONNECTION_MANAGER_NOT_SUPPORTED,
+ ANSWERED_ELSEWHERE,
+ CALL_PULLED
+ })
+ public @interface DisconnectCauseCode {}
/**
* Reason code (returned via {@link #getReason()}) which indicates that a call could not be
@@ -116,7 +142,7 @@
*/
public static final String REASON_EMERGENCY_CALL_PLACED = "REASON_EMERGENCY_CALL_PLACED";
- private int mDisconnectCode;
+ private @DisconnectCauseCode int mDisconnectCode;
private CharSequence mDisconnectLabel;
private CharSequence mDisconnectDescription;
private String mDisconnectReason;
@@ -130,7 +156,7 @@
*
* @param code The code for the disconnect cause.
*/
- public DisconnectCause(int code) {
+ public DisconnectCause(@DisconnectCauseCode int code) {
this(code, null, null, null, ToneGenerator.TONE_UNKNOWN);
}
@@ -140,7 +166,7 @@
* @param code The code for the disconnect cause.
* @param reason The reason for the disconnect.
*/
- public DisconnectCause(int code, String reason) {
+ public DisconnectCause(@DisconnectCauseCode int code, String reason) {
this(code, null, null, reason, ToneGenerator.TONE_UNKNOWN);
}
@@ -152,7 +178,8 @@
* @param description The localized description to show to the user to explain the disconnect.
* @param reason The reason for the disconnect.
*/
- public DisconnectCause(int code, CharSequence label, CharSequence description, String reason) {
+ public DisconnectCause(@DisconnectCauseCode int code, CharSequence label,
+ CharSequence description, String reason) {
this(code, label, description, reason, ToneGenerator.TONE_UNKNOWN);
}
@@ -165,8 +192,8 @@
* @param reason The reason for the disconnect.
* @param toneToPlay The tone to play on disconnect, as defined in {@link ToneGenerator}.
*/
- public DisconnectCause(int code, CharSequence label, CharSequence description, String reason,
- int toneToPlay) {
+ public DisconnectCause(@DisconnectCauseCode int code, CharSequence label,
+ CharSequence description, String reason, int toneToPlay) {
this(code, label, description, reason, toneToPlay,
android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
PreciseDisconnectCause.ERROR_UNSPECIFIED, null /* imsReasonInfo */);
@@ -186,8 +213,8 @@
* @param imsReasonInfo The relevant {@link ImsReasonInfo}, or {@code null} if not available.
* @hide
*/
- public DisconnectCause(int code, @NonNull CharSequence label,
- @NonNull CharSequence description, @NonNull String reason,
+ public DisconnectCause(@DisconnectCauseCode int code, @Nullable CharSequence label,
+ @Nullable CharSequence description, @Nullable String reason,
int toneToPlay, @Annotation.DisconnectCauses int telephonyDisconnectCause,
@Annotation.PreciseDisconnectCauses int telephonyPreciseDisconnectCause,
@Nullable ImsReasonInfo imsReasonInfo) {
@@ -206,7 +233,7 @@
*
* @return The disconnect code.
*/
- public int getCode() {
+ public @DisconnectCauseCode int getCode() {
return mDisconnectCode;
}
@@ -301,28 +328,24 @@
@SystemApi
@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
public static final class Builder {
- private int mDisconnectCode;
+ private @DisconnectCauseCode int mDisconnectCode;
private CharSequence mDisconnectLabel;
private CharSequence mDisconnectDescription;
private String mDisconnectReason;
- private int mToneToPlay;
+ private int mToneToPlay = ToneGenerator.TONE_UNKNOWN;
private int mTelephonyDisconnectCause;
private int mTelephonyPreciseDisconnectCause;
private ImsReasonInfo mImsReasonInfo;
- /**
- * Sets the code for the reason for this disconnect.
- * @param code The code denoting the type of disconnect.
- */
- public @NonNull DisconnectCause.Builder setCode(int code) {
+ public Builder(@DisconnectCauseCode int code) {
mDisconnectCode = code;
- return this;
}
/**
* Sets a label which explains the reason for the disconnect cause, used for display in the
* user interface.
* @param label The label to associate with the disconnect cause.
+ * @return The {@link DisconnectCause} builder instance.
*/
public @NonNull DisconnectCause.Builder setLabel(@Nullable CharSequence label) {
mDisconnectLabel = label;
@@ -333,6 +356,7 @@
* Sets a description which provides the reason for the disconnect cause, used for display
* in the user interface.
* @param description The description to associate with the disconnect cause.
+ * @return The {@link DisconnectCause} builder instance.
*/
public @NonNull DisconnectCause.Builder setDescription(
@Nullable CharSequence description) {
@@ -344,6 +368,7 @@
* Sets a reason providing explanation for the disconnect (intended for logging and not for
* displaying in the user interface).
* @param reason The reason for the disconnect.
+ * @return The {@link DisconnectCause} builder instance.
*/
public @NonNull DisconnectCause.Builder setReason(@NonNull String reason) {
mDisconnectReason = reason;
@@ -353,6 +378,7 @@
/**
* Sets the tone to play when disconnected.
* @param toneToPlay The tone as defined in {@link ToneGenerator} to play when disconnected.
+ * @return The {@link DisconnectCause} builder instance.
*/
public @NonNull DisconnectCause.Builder setTone(int toneToPlay) {
mToneToPlay = toneToPlay;
@@ -363,6 +389,7 @@
* Sets the telephony {@link android.telephony.DisconnectCause} for the call (used
* internally by Telecom for providing extra debug information from Telephony).
* @param telephonyDisconnectCause The disconnect cause as provided by Telephony.
+ * @return The {@link DisconnectCause} builder instance.
*/
public @NonNull DisconnectCause.Builder setTelephonyDisconnectCause(
@Annotation.DisconnectCauses int telephonyDisconnectCause) {
@@ -375,6 +402,7 @@
* internally by Telecom for providing extra debug information from Telephony).
* @param telephonyPreciseDisconnectCause The precise disconnect cause as provided by
* Telephony.
+ * @return The {@link DisconnectCause} builder instance.
*/
public @NonNull DisconnectCause.Builder setTelephonyPreciseDisconnectCause(
@@ -384,10 +412,11 @@
}
/**
- * Returns the telephony {@link ImsReasonInfo} associated with the call disconnection. This
+ * Sets the telephony {@link ImsReasonInfo} associated with the call disconnection. This
* is only used internally by Telecom for providing extra debug information from Telephony.
*
* @param imsReasonInfo The {@link ImsReasonInfo} or {@code null} if not known.
+ * @return The {@link DisconnectCause} builder instance.
*/
public @NonNull DisconnectCause.Builder setImsReasonInfo(
@Nullable ImsReasonInfo imsReasonInfo) {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index d3a50bb..df32fbd 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3315,6 +3315,18 @@
"support_no_reply_timer_for_cfnry_bool";
/**
+ * No reply time value to be sent to network for call forwarding on no reply
+ * (CFNRy 3GPP TS 24.082 version 17.0 section 3).
+ * Controls time in seconds for the no reply condition on in the call forwarding
+ * settings UI.
+ * This is available when {@link #KEY_SUPPORT_NO_REPLY_TIMER_FOR_CFNRY_BOOL} is true.
+ *
+ * @hide
+ */
+ public static final String KEY_NO_REPLY_TIMER_FOR_CFNRY_SEC_INT =
+ "no_reply_timer_for_cfnry_sec_int";
+
+ /**
* List of the FAC (feature access codes) to dial as a normal call.
* @hide
*/
@@ -10666,6 +10678,7 @@
sDefaults.putBoolean(KEY_VT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_RTT_CALL_BOOL, true);
sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORT_NO_REPLY_TIMER_FOR_CFNRY_BOOL, true);
+ sDefaults.putInt(KEY_NO_REPLY_TIMER_FOR_CFNRY_SEC_INT, 20);
sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL, false);
sDefaults.putBoolean(KEY_SHOW_PRECISE_FAILED_CAUSE_BOOL, false);
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index e3ce766..ebabbf9 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -512,6 +512,7 @@
EUICC_ACTIVATION_TYPE_BACKUP,
EUICC_ACTIVATION_TYPE_TRANSFER,
EUICC_ACTIVATION_TYPE_ACCOUNT_REQUIRED,
+ EUICC_ACTIVATION_TYPE_TRANSFER_FINAL_HOLD,
})
public @interface EuiccActivationType{}
@@ -555,6 +556,15 @@
public static final int EUICC_ACTIVATION_TYPE_ACCOUNT_REQUIRED = 4;
/**
+ * The activation flow of eSIM transfer to block the transfer process before B&R flow.
+ * This is needed to avoid connection overlapping between eSIM connection B&R connection.
+ *
+ * @hide
+ */
+ // TODO(b/329212614): add system api annotation during the allowed api timeline.
+ public static final int EUICC_ACTIVATION_TYPE_TRANSFER_FINAL_HOLD = 5;
+
+ /**
* Euicc OTA update status which can be got by {@link #getOtaStatus}
* @removed mistakenly exposed as system-api previously
*/
diff --git a/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png b/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
index 443de8e..7cdab94 100644
--- a/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
+++ b/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
index c51da05..377288d 100644
--- a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
+++ b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png b/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
index ab23401..68b1473 100644
--- a/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
+++ b/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
Binary files differ
diff --git a/tools/aapt2/optimize/VersionCollapser.cpp b/tools/aapt2/optimize/VersionCollapser.cpp
index cd791bd..27fff9a 100644
--- a/tools/aapt2/optimize/VersionCollapser.cpp
+++ b/tools/aapt2/optimize/VersionCollapser.cpp
@@ -70,7 +70,7 @@
* exception is when there is no exact matching resource for the minSdk. The next smallest one will
* be kept.
*/
-static void CollapseVersions(int min_sdk, ResourceEntry* entry) {
+static void CollapseVersions(IAaptContext* context, int min_sdk, ResourceEntry* entry) {
// First look for all sdks less than minSdk.
for (auto iter = entry->values.rbegin(); iter != entry->values.rend();
++iter) {
@@ -102,7 +102,14 @@
auto filter_iter =
make_filter_iterator(iter + 1, entry->values.rend(), pred);
while (filter_iter.HasNext()) {
- filter_iter.Next() = {};
+ auto& next = filter_iter.Next();
+ if (context->IsVerbose()) {
+ context->GetDiagnostics()->Note(android::DiagMessage()
+ << "removing configuration " << next->config.to_string()
+ << " for entry: " << entry->name
+ << ", because its SDK version is smaller than minSdk");
+ }
+ next = {};
}
}
}
@@ -126,6 +133,12 @@
util::make_unique<ResourceConfigValue>(
config_value->config.CopyWithoutSdkVersion(),
config_value->product);
+ if (context->IsVerbose()) {
+ context->GetDiagnostics()->Note(android::DiagMessage()
+ << "overriding resource: " << entry->name
+ << ", removing SDK version from configuration "
+ << config_value->config.to_string());
+ }
new_value->value = std::move(config_value->value);
config_value = std::move(new_value);
@@ -147,10 +160,14 @@
bool VersionCollapser::Consume(IAaptContext* context, ResourceTable* table) {
TRACE_NAME("VersionCollapser::Consume");
const int min_sdk = context->GetMinSdkVersion();
+ if (context->IsVerbose()) {
+ context->GetDiagnostics()->Note(android::DiagMessage()
+ << "Running VersionCollapser with minSdk = " << min_sdk);
+ }
for (auto& package : table->packages) {
for (auto& type : package->types) {
for (auto& entry : type->entries) {
- CollapseVersions(min_sdk, entry.get());
+ CollapseVersions(context, min_sdk, entry.get());
}
}
}