diff options
470 files changed, 9929 insertions, 4791 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 2f843f9d6164..8547ec164084 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -1809,12 +1809,25 @@ aconfig_declarations { name: "aconfig_settingslib_flags", package: "com.android.settingslib.flags", container: "system", + exportable: true, srcs: [ "packages/SettingsLib/aconfig/settingslib.aconfig", ], } java_aconfig_library { + name: "aconfig_settingslib_exported_flags_java_lib", + aconfig_declarations: "aconfig_settingslib_flags", + defaults: ["framework-minus-apex-aconfig-java-defaults"], + mode: "exported", + min_sdk_version: "30", + apex_available: [ + "//apex_available:platform", + "com.android.permission", + ], +} + +java_aconfig_library { name: "aconfig_settingslib_flags_java_lib", aconfig_declarations: "aconfig_settingslib_flags", defaults: ["framework-minus-apex-aconfig-java-defaults"], diff --git a/MEMORY_OWNERS b/MEMORY_OWNERS index 89ce5140d8ea..12aa2951bbc9 100644 --- a/MEMORY_OWNERS +++ b/MEMORY_OWNERS @@ -2,5 +2,4 @@ surenb@google.com tjmercier@google.com kaleshsingh@google.com jyescas@google.com -carlosgalo@google.com jji@google.com diff --git a/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java b/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java index c77528021201..ed669beae1ce 100644 --- a/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java +++ b/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java @@ -63,12 +63,14 @@ public class ZipFilePerfTest { @Test @Parameters(method = "getData") - public void timeZipFileOpenClose(int numEntries) throws Exception { + public void timeZipFileOpen(int numEntries) throws Exception { setUp(numEntries); BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { ZipFile zf = new ZipFile(mFile); + state.pauseTiming(); zf.close(); + state.resumeTiming(); } } diff --git a/api/api.go b/api/api.go index e4d783eba4c3..cbdb7e81ab86 100644 --- a/api/api.go +++ b/api/api.go @@ -105,7 +105,7 @@ func (a *CombinedApis) DepsMutator(ctx android.BottomUpMutatorContext) { func (a *CombinedApis) GenerateAndroidBuildActions(ctx android.ModuleContext) { ctx.WalkDeps(func(child, parent android.Module) bool { - if _, ok := child.(java.AndroidLibraryDependency); ok && child.Name() != "framework-res" { + if _, ok := android.OtherModuleProvider(ctx, child, java.AndroidLibraryInfoProvider); ok && child.Name() != "framework-res" { // Stubs of BCP and SSCP libraries should not have any dependencies on apps // This check ensures that we do not run into circular dependencies when UNBUNDLED_BUILD_TARGET_SDK_WITH_API_FINGERPRINT=true ctx.ModuleErrorf( diff --git a/core/api/current.txt b/core/api/current.txt index 32507dfef9b6..7c56a5811abb 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9190,37 +9190,37 @@ package android.app.blob { package android.app.jank { @FlaggedApi("android.app.jank.detailed_app_jank_metrics_api") public final class AppJankStats { - ctor public AppJankStats(int, @NonNull String, @Nullable String, @Nullable String, long, long, @NonNull android.app.jank.FrameOverrunHistogram); - method @NonNull public android.app.jank.FrameOverrunHistogram getFrameOverrunHistogram(); + ctor public AppJankStats(int, @NonNull String, @Nullable String, @Nullable String, long, long, @NonNull android.app.jank.RelativeFrameTimeHistogram); method public long getJankyFrameCount(); + method @NonNull public android.app.jank.RelativeFrameTimeHistogram getRelativeFrameTimeHistogram(); method public long getTotalFrameCount(); method public int getUid(); method @NonNull public String getWidgetCategory(); method @NonNull public String getWidgetId(); method @NonNull public String getWidgetState(); - field public static final String ANIMATING = "animating"; - field public static final String ANIMATION = "animation"; - field public static final String DRAGGING = "dragging"; - field public static final String FLINGING = "flinging"; - field public static final String KEYBOARD = "keyboard"; - field public static final String MEDIA = "media"; - field public static final String NAVIGATION = "navigation"; - field public static final String NONE = "none"; - field public static final String OTHER = "other"; - field public static final String PLAYBACK = "playback"; - field public static final String PREDICTIVE_BACK = "predictive_back"; - field public static final String SCROLL = "scroll"; - field public static final String SCROLLING = "scrolling"; - field public static final String SWIPING = "swiping"; - field public static final String TAPPING = "tapping"; - field public static final String WIDGET_CATEGORY_UNSPECIFIED = "widget_category_unspecified"; - field public static final String WIDGET_STATE_UNSPECIFIED = "widget_state_unspecified"; - field public static final String ZOOMING = "zooming"; - } - - @FlaggedApi("android.app.jank.detailed_app_jank_metrics_api") public class FrameOverrunHistogram { - ctor public FrameOverrunHistogram(); - method public void addFrameOverrunMillis(int); + field public static final String WIDGET_CATEGORY_ANIMATION = "animation"; + field public static final String WIDGET_CATEGORY_KEYBOARD = "keyboard"; + field public static final String WIDGET_CATEGORY_MEDIA = "media"; + field public static final String WIDGET_CATEGORY_NAVIGATION = "navigation"; + field public static final String WIDGET_CATEGORY_OTHER = "other"; + field public static final String WIDGET_CATEGORY_SCROLL = "scroll"; + field public static final String WIDGET_CATEGORY_UNSPECIFIED = "unspecified"; + field public static final String WIDGET_STATE_ANIMATING = "animating"; + field public static final String WIDGET_STATE_DRAGGING = "dragging"; + field public static final String WIDGET_STATE_FLINGING = "flinging"; + field public static final String WIDGET_STATE_NONE = "none"; + field public static final String WIDGET_STATE_PLAYBACK = "playback"; + field public static final String WIDGET_STATE_PREDICTIVE_BACK = "predictive_back"; + field public static final String WIDGET_STATE_SCROLLING = "scrolling"; + field public static final String WIDGET_STATE_SWIPING = "swiping"; + field public static final String WIDGET_STATE_TAPPING = "tapping"; + field public static final String WIDGET_STATE_UNSPECIFIED = "unspecified"; + field public static final String WIDGET_STATE_ZOOMING = "zooming"; + } + + @FlaggedApi("android.app.jank.detailed_app_jank_metrics_api") public class RelativeFrameTimeHistogram { + ctor public RelativeFrameTimeHistogram(); + method public void addRelativeFrameTimeMillis(int); method @NonNull public int[] getBucketCounters(); method @NonNull public int[] getBucketEndpointsMillis(); } @@ -42521,7 +42521,7 @@ package android.service.settings.preferences { field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.GetValueRequest> CREATOR; } - public static final class GetValueRequest.Builder { + @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final class GetValueRequest.Builder { ctor public GetValueRequest.Builder(@NonNull String, @NonNull String); method @NonNull public android.service.settings.preferences.GetValueRequest build(); } @@ -42542,7 +42542,7 @@ package android.service.settings.preferences { field public static final int RESULT_UNSUPPORTED = 1; // 0x1 } - public static final class GetValueResult.Builder { + @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final class GetValueResult.Builder { ctor public GetValueResult.Builder(int); method @NonNull public android.service.settings.preferences.GetValueResult build(); method @NonNull public android.service.settings.preferences.GetValueResult.Builder setMetadata(@Nullable android.service.settings.preferences.SettingsPreferenceMetadata); @@ -42555,7 +42555,7 @@ package android.service.settings.preferences { field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.MetadataRequest> CREATOR; } - public static final class MetadataRequest.Builder { + @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final class MetadataRequest.Builder { ctor public MetadataRequest.Builder(); method @NonNull public android.service.settings.preferences.MetadataRequest build(); } @@ -42571,7 +42571,7 @@ package android.service.settings.preferences { field public static final int RESULT_UNSUPPORTED = 1; // 0x1 } - public static final class MetadataResult.Builder { + @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final class MetadataResult.Builder { ctor public MetadataResult.Builder(int); method @NonNull public android.service.settings.preferences.MetadataResult build(); method @NonNull public android.service.settings.preferences.MetadataResult.Builder setMetadataList(@NonNull java.util.List<android.service.settings.preferences.SettingsPreferenceMetadata>); @@ -42586,7 +42586,7 @@ package android.service.settings.preferences { field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.SetValueRequest> CREATOR; } - public static final class SetValueRequest.Builder { + @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final class SetValueRequest.Builder { ctor public SetValueRequest.Builder(@NonNull String, @NonNull String, @NonNull android.service.settings.preferences.SettingsPreferenceValue); method @NonNull public android.service.settings.preferences.SetValueRequest build(); } @@ -42608,14 +42608,13 @@ package android.service.settings.preferences { field public static final int RESULT_UNSUPPORTED = 1; // 0x1 } - public static final class SetValueResult.Builder { + @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final class SetValueResult.Builder { ctor public SetValueResult.Builder(int); method @NonNull public android.service.settings.preferences.SetValueResult build(); } @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class SettingsPreferenceMetadata implements android.os.Parcelable { method public int describeContents(); - method @NonNull public java.util.List<java.lang.String> getBreadcrumbs(); method @NonNull public android.os.Bundle getExtras(); method @NonNull public String getKey(); method @Nullable public android.content.Intent getLaunchIntent(); @@ -42631,17 +42630,16 @@ package android.service.settings.preferences { method public boolean isWritable(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.SettingsPreferenceMetadata> CREATOR; + field public static final int DEEPLINK_ONLY = 2; // 0x2 field public static final int EXPECT_POST_CONFIRMATION = 1; // 0x1 - field public static final int EXPECT_PRE_CONFIRMATION = 2; // 0x2 field public static final int NO_DIRECT_ACCESS = 3; // 0x3 field public static final int NO_SENSITIVITY = 0; // 0x0 } - public static final class SettingsPreferenceMetadata.Builder { + @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final class SettingsPreferenceMetadata.Builder { ctor public SettingsPreferenceMetadata.Builder(@NonNull String, @NonNull String); method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata build(); method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setAvailable(boolean); - method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setBreadcrumbs(@NonNull java.util.List<java.lang.String>); method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setEnabled(boolean); method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setExtras(@NonNull android.os.Bundle); method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setLaunchIntent(@Nullable android.content.Intent); @@ -42688,7 +42686,7 @@ package android.service.settings.preferences { field public static final int TYPE_STRING = 3; // 0x3 } - public static final class SettingsPreferenceValue.Builder { + @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final class SettingsPreferenceValue.Builder { ctor public SettingsPreferenceValue.Builder(int); method @NonNull public android.service.settings.preferences.SettingsPreferenceValue build(); method @NonNull public android.service.settings.preferences.SettingsPreferenceValue.Builder setBooleanValue(boolean); @@ -53484,9 +53482,9 @@ package android.view { field public static final int CHANGE_FRAME_RATE_ALWAYS = 1; // 0x1 field public static final int CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS = 0; // 0x0 field @NonNull public static final android.os.Parcelable.Creator<android.view.Surface> CREATOR; + field @FlaggedApi("com.android.graphics.surfaceflinger.flags.arr_setframerate_gte_enum") public static final int FRAME_RATE_COMPATIBILITY_AT_LEAST = 2; // 0x2 field public static final int FRAME_RATE_COMPATIBILITY_DEFAULT = 0; // 0x0 field public static final int FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1; // 0x1 - field @FlaggedApi("com.android.graphics.surfaceflinger.flags.arr_setframerate_gte_enum") public static final int FRAME_RATE_COMPATIBILITY_GTE = 2; // 0x2 field public static final int ROTATION_0 = 0; // 0x0 field public static final int ROTATION_180 = 2; // 0x2 field public static final int ROTATION_270 = 3; // 0x3 @@ -57120,6 +57118,7 @@ package android.view.contentcapture { method public void close(); method @NonNull public final android.view.contentcapture.ContentCaptureSession createContentCaptureSession(@NonNull android.view.contentcapture.ContentCaptureContext); method public final void destroy(); + method @FlaggedApi("android.view.contentcapture.flags.ccapi_baklava_enabled") public void flush(); method @Nullable public final android.view.contentcapture.ContentCaptureContext getContentCaptureContext(); method @NonNull public final android.view.contentcapture.ContentCaptureSessionId getContentCaptureSessionId(); method @NonNull public android.view.autofill.AutofillId newAutofillId(@NonNull android.view.autofill.AutofillId, long); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 6d24e4684e77..d5cb6c034699 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -5079,8 +5079,8 @@ package android.hardware.contexthub { } @FlaggedApi("android.chre.flags.offload_api") public class HubEndpoint { - method @Nullable public android.hardware.contexthub.IHubEndpointLifecycleCallback getLifecycleCallback(); - method @Nullable public android.hardware.contexthub.IHubEndpointMessageCallback getMessageCallback(); + method @Nullable public android.hardware.contexthub.HubEndpointLifecycleCallback getLifecycleCallback(); + method @Nullable public android.hardware.contexthub.HubEndpointMessageCallback getMessageCallback(); method @NonNull public java.util.Collection<android.hardware.contexthub.HubServiceInfo> getServiceInfoCollection(); method @Nullable public String getTag(); method public int getVersion(); @@ -5095,14 +5095,19 @@ package android.hardware.contexthub { public static final class HubEndpoint.Builder { ctor public HubEndpoint.Builder(@NonNull android.content.Context); method @NonNull public android.hardware.contexthub.HubEndpoint build(); - method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull android.hardware.contexthub.IHubEndpointLifecycleCallback); - method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.IHubEndpointLifecycleCallback); - method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setMessageCallback(@NonNull android.hardware.contexthub.IHubEndpointMessageCallback); - method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setMessageCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.IHubEndpointMessageCallback); + method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull android.hardware.contexthub.HubEndpointLifecycleCallback); + method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.HubEndpointLifecycleCallback); + method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setMessageCallback(@NonNull android.hardware.contexthub.HubEndpointMessageCallback); + method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setMessageCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.HubEndpointMessageCallback); method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setServiceInfoCollection(@NonNull java.util.Collection<android.hardware.contexthub.HubServiceInfo>); method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setTag(@NonNull String); } + @FlaggedApi("android.chre.flags.offload_api") public interface HubEndpointDiscoveryCallback { + method public void onEndpointsStarted(@NonNull java.util.List<android.hardware.contexthub.HubDiscoveryInfo>); + method public void onEndpointsStopped(@NonNull java.util.List<android.hardware.contexthub.HubDiscoveryInfo>, int); + } + @FlaggedApi("android.chre.flags.offload_api") public final class HubEndpointInfo implements android.os.Parcelable { method public int describeContents(); method @NonNull public android.hardware.contexthub.HubEndpointInfo.HubEndpointIdentifier getIdentifier(); @@ -5126,6 +5131,16 @@ package android.hardware.contexthub { method public long getHub(); } + @FlaggedApi("android.chre.flags.offload_api") public interface HubEndpointLifecycleCallback { + method public void onSessionClosed(@NonNull android.hardware.contexthub.HubEndpointSession, int); + method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo, @Nullable String); + method public void onSessionOpened(@NonNull android.hardware.contexthub.HubEndpointSession); + } + + @FlaggedApi("android.chre.flags.offload_api") public interface HubEndpointMessageCallback { + method public void onMessageReceived(@NonNull android.hardware.contexthub.HubEndpointSession, @NonNull android.hardware.contexthub.HubMessage); + } + @FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSession implements java.lang.AutoCloseable { method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void close(); method @Nullable public String getServiceDescriptor(); @@ -5174,21 +5189,6 @@ package android.hardware.contexthub { method @NonNull public android.hardware.contexthub.HubServiceInfo build(); } - @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointDiscoveryCallback { - method public void onEndpointsStarted(@NonNull java.util.List<android.hardware.contexthub.HubDiscoveryInfo>); - method public void onEndpointsStopped(@NonNull java.util.List<android.hardware.contexthub.HubDiscoveryInfo>, int); - } - - @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointLifecycleCallback { - method public void onSessionClosed(@NonNull android.hardware.contexthub.HubEndpointSession, int); - method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo, @Nullable String); - method public void onSessionOpened(@NonNull android.hardware.contexthub.HubEndpointSession); - } - - @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointMessageCallback { - method public void onMessageReceived(@NonNull android.hardware.contexthub.HubEndpointSession, @NonNull android.hardware.contexthub.HubMessage); - } - } package android.hardware.devicestate { @@ -6192,16 +6192,16 @@ package android.hardware.location { method @Deprecated public int registerCallback(@NonNull android.hardware.location.ContextHubManager.Callback); method @Deprecated public int registerCallback(android.hardware.location.ContextHubManager.Callback, android.os.Handler); method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpoint(@NonNull android.hardware.contexthub.HubEndpoint); - method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(long, @NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback); - method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(long, @NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback, @NonNull java.util.concurrent.Executor); - method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull String, @NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback); - method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull String, @NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback, @NonNull java.util.concurrent.Executor); + method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull android.hardware.contexthub.HubEndpointDiscoveryCallback, long); + method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.HubEndpointDiscoveryCallback, long); + method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull android.hardware.contexthub.HubEndpointDiscoveryCallback, @NonNull String); + method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.HubEndpointDiscoveryCallback, @NonNull String); method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int sendMessage(int, int, @NonNull android.hardware.location.ContextHubMessage); method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int unloadNanoApp(int); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> unloadNanoApp(@NonNull android.hardware.location.ContextHubInfo, long); method @Deprecated public int unregisterCallback(@NonNull android.hardware.location.ContextHubManager.Callback); method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void unregisterEndpoint(@NonNull android.hardware.contexthub.HubEndpoint); - method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void unregisterEndpointDiscoveryCallback(@NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback); + method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void unregisterEndpointDiscoveryCallback(@NonNull android.hardware.contexthub.HubEndpointDiscoveryCallback); field public static final int AUTHORIZATION_DENIED = 0; // 0x0 field public static final int AUTHORIZATION_DENIED_GRACE_PERIOD = 1; // 0x1 field public static final int AUTHORIZATION_GRANTED = 2; // 0x2 @@ -19091,6 +19091,7 @@ package android.view.contentcapture { method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.view.contentcapture.ContentCaptureEvent> CREATOR; field public static final int TYPE_CONTEXT_UPDATED = 6; // 0x6 + field @FlaggedApi("android.view.contentcapture.flags.ccapi_baklava_enabled") public static final int TYPE_SESSION_FLUSH = 11; // 0xb field public static final int TYPE_SESSION_PAUSED = 8; // 0x8 field public static final int TYPE_SESSION_RESUMED = 7; // 0x7 field public static final int TYPE_VIEW_APPEARED = 1; // 0x1 diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index fee8cdb1ce51..c3ef104075f2 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -5834,7 +5834,11 @@ public class Activity extends ContextThemeWrapper final int size = permissions.length; int[] results = new int[size]; for (int i = 0; i < size; i++) { - results[i] = deviceContext.getPermissionRequestState(permissions[i]); + if (permissions[i] == null) { + results[i] = Context.PERMISSION_REQUEST_STATE_UNREQUESTABLE; + } else { + results[i] = deviceContext.getPermissionRequestState(permissions[i]); + } } return results; } diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index abdfb53537f8..999db18a1229 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -485,6 +485,11 @@ public abstract class ActivityManagerInternal { */ public static final int OOM_ADJ_REASON_FOLLOW_UP = 23; + /** + * Oom Adj Reason: Update after oom adjuster configuration has changed. + */ + public static final int OOM_ADJ_REASON_RECONFIGURATION = 24; + @IntDef(prefix = {"OOM_ADJ_REASON_"}, value = { OOM_ADJ_REASON_NONE, OOM_ADJ_REASON_ACTIVITY, @@ -510,6 +515,7 @@ public abstract class ActivityManagerInternal { OOM_ADJ_REASON_RESTRICTION_CHANGE, OOM_ADJ_REASON_COMPONENT_DISABLED, OOM_ADJ_REASON_FOLLOW_UP, + OOM_ADJ_REASON_RECONFIGURATION, }) @Retention(RetentionPolicy.SOURCE) public @interface OomAdjReason {} diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java index 61b56877589b..599f1a8be233 100644 --- a/core/java/android/app/AppCompatTaskInfo.java +++ b/core/java/android/app/AppCompatTaskInfo.java @@ -27,6 +27,7 @@ import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; /** * Stores App Compat information about a particular Task. @@ -58,16 +59,11 @@ public class AppCompatTaskInfo implements Parcelable { public int topActivityLetterboxHeight = PROPERTY_VALUE_UNSET; /** - * Contains the current app height of the letterboxed activity if available or - * {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise. + * Contains the app bounds of the top activity or size compat mode + * bounds when in size compat mode. If null, contains bounds. */ - public int topActivityLetterboxAppHeight = PROPERTY_VALUE_UNSET; - - /** - * Contains the current app width of the letterboxed activity if available or - * {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise. - */ - public int topActivityLetterboxAppWidth = PROPERTY_VALUE_UNSET; + @NonNull + public final Rect topActivityAppBounds = new Rect(); /** * Contains the top activity bounds when the activity is letterboxed. @@ -350,8 +346,7 @@ public class AppCompatTaskInfo implements Parcelable { && topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition && topActivityLetterboxWidth == that.topActivityLetterboxWidth && topActivityLetterboxHeight == that.topActivityLetterboxHeight - && topActivityLetterboxAppWidth == that.topActivityLetterboxAppWidth - && topActivityLetterboxAppHeight == that.topActivityLetterboxAppHeight + && topActivityAppBounds.equals(that.topActivityAppBounds) && topActivityLetterboxHorizontalPosition == that.topActivityLetterboxHorizontalPosition && cameraCompatTaskInfo.equalsForTaskOrganizer(that.cameraCompatTaskInfo); @@ -371,8 +366,7 @@ public class AppCompatTaskInfo implements Parcelable { == that.topActivityLetterboxHorizontalPosition && topActivityLetterboxWidth == that.topActivityLetterboxWidth && topActivityLetterboxHeight == that.topActivityLetterboxHeight - && topActivityLetterboxAppWidth == that.topActivityLetterboxAppWidth - && topActivityLetterboxAppHeight == that.topActivityLetterboxAppHeight + && topActivityAppBounds.equals(that.topActivityAppBounds) && cameraCompatTaskInfo.equalsForCompatUi(that.cameraCompatTaskInfo); } @@ -385,8 +379,7 @@ public class AppCompatTaskInfo implements Parcelable { topActivityLetterboxHorizontalPosition = source.readInt(); topActivityLetterboxWidth = source.readInt(); topActivityLetterboxHeight = source.readInt(); - topActivityLetterboxAppWidth = source.readInt(); - topActivityLetterboxAppHeight = source.readInt(); + topActivityAppBounds.set(Objects.requireNonNull(source.readTypedObject(Rect.CREATOR))); topActivityLetterboxBounds = source.readTypedObject(Rect.CREATOR); cameraCompatTaskInfo = source.readTypedObject(CameraCompatTaskInfo.CREATOR); } @@ -401,8 +394,7 @@ public class AppCompatTaskInfo implements Parcelable { dest.writeInt(topActivityLetterboxHorizontalPosition); dest.writeInt(topActivityLetterboxWidth); dest.writeInt(topActivityLetterboxHeight); - dest.writeInt(topActivityLetterboxAppWidth); - dest.writeInt(topActivityLetterboxAppHeight); + dest.writeTypedObject(topActivityAppBounds, flags); dest.writeTypedObject(topActivityLetterboxBounds, flags); dest.writeTypedObject(cameraCompatTaskInfo, flags); } @@ -421,8 +413,7 @@ public class AppCompatTaskInfo implements Parcelable { + topActivityLetterboxHorizontalPosition + " topActivityLetterboxWidth=" + topActivityLetterboxWidth + " topActivityLetterboxHeight=" + topActivityLetterboxHeight - + " topActivityLetterboxAppWidth=" + topActivityLetterboxAppWidth - + " topActivityLetterboxAppHeight=" + topActivityLetterboxAppHeight + + " topActivityAppBounds=" + topActivityAppBounds + " isUserFullscreenOverrideEnabled=" + isUserFullscreenOverrideEnabled() + " isSystemFullscreenOverrideEnabled=" + isSystemFullscreenOverrideEnabled() + " hasMinAspectRatioOverride=" + hasMinAspectRatioOverride() diff --git a/core/java/android/app/IUserSwitchObserver.aidl b/core/java/android/app/IUserSwitchObserver.aidl index 1ff7a17e578f..d71ee7c712e7 100644 --- a/core/java/android/app/IUserSwitchObserver.aidl +++ b/core/java/android/app/IUserSwitchObserver.aidl @@ -19,10 +19,10 @@ package android.app; import android.os.IRemoteCallback; /** {@hide} */ -interface IUserSwitchObserver { - void onBeforeUserSwitching(int newUserId); - oneway void onUserSwitching(int newUserId, IRemoteCallback reply); - oneway void onUserSwitchComplete(int newUserId); - oneway void onForegroundProfileSwitch(int newProfileId); - oneway void onLockedBootComplete(int newUserId); +oneway interface IUserSwitchObserver { + void onBeforeUserSwitching(int newUserId, IRemoteCallback reply); + void onUserSwitching(int newUserId, IRemoteCallback reply); + void onUserSwitchComplete(int newUserId); + void onForegroundProfileSwitch(int newProfileId); + void onLockedBootComplete(int newUserId); } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 17638ee76dba..eeb1ebb69b03 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -11208,8 +11208,8 @@ public class Notification implements Parcelable private static final String KEY_SEGMENT_LENGTH = "length"; private static final String KEY_POINT_POSITION = "position"; - private static final int MAX_PROGRESS_SEGMENT_LIMIT = 15; - private static final int MAX_PROGRESS_STOP_LIMIT = 5; + private static final int MAX_PROGRESS_SEGMENT_LIMIT = 10; + private static final int MAX_PROGRESS_POINT_LIMIT = 4; private static final int DEFAULT_PROGRESS_MAX = 100; private List<Segment> mProgressSegments = new ArrayList<>(); @@ -11286,7 +11286,9 @@ public class Notification implements Parcelable mProgressSegments = new ArrayList<>(); } mProgressSegments.clear(); - mProgressSegments.addAll(progressSegments); + for (Segment segment : progressSegments) { + addProgressSegment(segment); + } return this; } @@ -11302,7 +11304,11 @@ public class Notification implements Parcelable if (mProgressSegments == null) { mProgressSegments = new ArrayList<>(); } - mProgressSegments.add(segment); + if (segment.getLength() > 0) { + mProgressSegments.add(segment); + } else { + Log.w(TAG, "Dropped the segment. The length is not a positive integer."); + } return this; } @@ -11327,7 +11333,14 @@ public class Notification implements Parcelable * @see Point */ public @NonNull ProgressStyle setProgressPoints(@NonNull List<Point> points) { - mProgressPoints = new ArrayList<>(points); + if (mProgressPoints == null) { + mProgressPoints = new ArrayList<>(); + } + mProgressPoints.clear(); + + for (Point point: points) { + addProgressPoint(point); + } return this; } @@ -11348,7 +11361,17 @@ public class Notification implements Parcelable if (mProgressPoints == null) { mProgressPoints = new ArrayList<>(); } - mProgressPoints.add(point); + if (point.getPosition() >= 0) { + mProgressPoints.add(point); + + if (mProgressPoints.size() > MAX_PROGRESS_POINT_LIMIT) { + Log.w(TAG, "Progress points limit is reached. First" + + MAX_PROGRESS_POINT_LIMIT + " points will be rendered."); + } + + } else { + Log.w(TAG, "Dropped the point. The position is a negative integer."); + } return this; } @@ -11384,8 +11407,7 @@ public class Notification implements Parcelable } else { int progressMax = 0; int validSegmentCount = 0; - for (int i = 0; i < progressSegment.size() - && validSegmentCount < MAX_PROGRESS_SEGMENT_LIMIT; i++) { + for (int i = 0; i < progressSegment.size(); i++) { int segmentLength = progressSegment.get(i).getLength(); if (segmentLength > 0) { try { @@ -11832,6 +11854,30 @@ public class Notification implements Parcelable totalLength = DEFAULT_PROGRESS_MAX; segments.add(sanitizeSegment(new Segment(totalLength), backgroundColor, defaultProgressColor)); + } else if (segments.size() > MAX_PROGRESS_SEGMENT_LIMIT) { + // If segment limit is exceeded. All segments will be replaced + // with a single segment + boolean allSameColor = true; + int firstSegmentColor = segments.get(0).getColor(); + + for (int i = 1; i < segments.size(); i++) { + if (segments.get(i).getColor() != firstSegmentColor) { + allSameColor = false; + break; + } + } + + // This single segment length has same max as total. + final Segment singleSegment = new Segment(totalLength); + // Single segment color: if all segments have the same color, + // use that color. Otherwise, use 0 / default. + singleSegment.setColor(allSameColor ? firstSegmentColor + : Notification.COLOR_DEFAULT); + + segments.clear(); + segments.add(sanitizeSegment(singleSegment, + backgroundColor, + defaultProgressColor)); } // Ensure point color contrasts. @@ -11840,6 +11886,9 @@ public class Notification implements Parcelable final int position = point.getPosition(); if (position < 0 || position > totalLength) continue; points.add(sanitizePoint(point, backgroundColor, defaultProgressColor)); + if (points.size() == MAX_PROGRESS_POINT_LIMIT) { + break; + } } model = new NotificationProgressModel(segments, points, @@ -11868,8 +11917,10 @@ public class Notification implements Parcelable * has the same hue as the original color, but is lightened or darkened depending on * whether the background is dark or light. * + * @hide */ - private int sanitizeProgressColor(@ColorInt int color, + @VisibleForTesting + public static int sanitizeProgressColor(@ColorInt int color, @ColorInt int bg, @ColorInt int defaultColor) { return Builder.ensureColorContrast( diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 8ed66eb7e6c0..e9b889a2f1aa 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -55,6 +55,7 @@ import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; import android.os.StrictMode; +import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings.Global; @@ -677,9 +678,14 @@ public class NotificationManager { } /** {@hide} */ - @UnsupportedAppUsage - public NotificationManager(Context context, InstantSource clock) + public NotificationManager(Context context) { + this(context, SystemClock.elapsedRealtimeClock()); + } + + /** {@hide} */ + @UnsupportedAppUsage + public NotificationManager(Context context, InstantSource clock) { mContext = context; mClock = clock; } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 920b19cd8f78..0bbe9434293a 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -17,7 +17,7 @@ package android.app; import static android.app.appfunctions.flags.Flags.enableAppFunctionManager; -import static android.provider.flags.Flags.stageFlagsForBuild; +import static android.provider.flags.Flags.newStoragePublicApi; import static android.server.Flags.removeGameManagerServiceFromWear; import android.accounts.AccountManager; @@ -289,7 +289,6 @@ import com.android.internal.os.IDropBoxManagerService; import com.android.internal.policy.PhoneLayoutInflater; import com.android.internal.util.Preconditions; -import java.time.InstantSource; import java.util.Map; import java.util.Objects; @@ -625,8 +624,8 @@ public final class SystemServiceRegistry { com.android.internal.R.style.Theme_Dialog, com.android.internal.R.style.Theme_Holo_Dialog, com.android.internal.R.style.Theme_DeviceDefault_Dialog, - com.android.internal.R.style.Theme_DeviceDefault_Light_Dialog)), - InstantSource.system()); + com.android.internal.R.style.Theme_DeviceDefault_Light_Dialog)) + ); }}); registerService(Context.PEOPLE_SERVICE, PeopleManager.class, @@ -1841,7 +1840,7 @@ public final class SystemServiceRegistry { VirtualizationFrameworkInitializer.registerServiceWrappers(); ConnectivityFrameworkInitializerBaklava.registerServiceWrappers(); - if (stageFlagsForBuild()) { + if (newStoragePublicApi()) { ConfigInfrastructureFrameworkInitializer.registerServiceWrappers(); } diff --git a/core/java/android/app/UserSwitchObserver.java b/core/java/android/app/UserSwitchObserver.java index 727799a1f948..1664cfb6f7a8 100644 --- a/core/java/android/app/UserSwitchObserver.java +++ b/core/java/android/app/UserSwitchObserver.java @@ -30,7 +30,11 @@ public class UserSwitchObserver extends IUserSwitchObserver.Stub { } @Override - public void onBeforeUserSwitching(int newUserId) throws RemoteException {} + public void onBeforeUserSwitching(int newUserId, IRemoteCallback reply) throws RemoteException { + if (reply != null) { + reply.sendResult(null); + } + } @Override public void onUserSwitching(int newUserId, IRemoteCallback reply) throws RemoteException { diff --git a/core/java/android/app/contextualsearch/ContextualSearchManager.java b/core/java/android/app/contextualsearch/ContextualSearchManager.java index 3438cc861661..ad43f271a910 100644 --- a/core/java/android/app/contextualsearch/ContextualSearchManager.java +++ b/core/java/android/app/contextualsearch/ContextualSearchManager.java @@ -48,7 +48,9 @@ public final 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. + * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH. + * + * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH */ public static final String EXTRA_ENTRYPOINT = "android.app.contextualsearch.extra.ENTRYPOINT"; @@ -56,14 +58,18 @@ public final class ContextualSearchManager { /** * 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. + * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH. + * + * @see #ACTION_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. + * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH. + * + * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH */ public static final String EXTRA_SCREENSHOT = "android.app.contextualsearch.extra.SCREENSHOT"; @@ -71,7 +77,9 @@ public final class ContextualSearchManager { /** * 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. + * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH. + * + * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH */ public static final String EXTRA_IS_MANAGED_PROFILE_VISIBLE = "android.app.contextualsearch.extra.IS_MANAGED_PROFILE_VISIBLE"; @@ -79,7 +87,9 @@ public final class ContextualSearchManager { /** * 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. + * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH. + * + * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH */ public static final String EXTRA_VISIBLE_PACKAGE_NAMES = "android.app.contextualsearch.extra.VISIBLE_PACKAGE_NAMES"; @@ -87,7 +97,9 @@ public final class ContextualSearchManager { /** * Key to get the time the user made the invocation request, based on * {@link SystemClock#uptimeMillis()}. - * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH. + * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH. + * + * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH * * TODO: un-hide in W * @@ -99,11 +111,24 @@ public final class ContextualSearchManager { /** * Key to get the binder token from the extras of the activity launched by contextual search. * This token is needed to invoke {@link CallbackToken#getContextualSearchState} method. - * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH. + * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH. + * + * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH */ public static final String EXTRA_TOKEN = "android.app.contextualsearch.extra.TOKEN"; /** + * Key to check whether audio is playing when contextual search is invoked. + * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH. + * + * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH + * + * @hide + */ + public static final String EXTRA_IS_AUDIO_PLAYING = + "android.app.contextualsearch.extra.IS_AUDIO_PLAYING"; + + /** * 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> diff --git a/core/java/android/app/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig index e8cfd79c9cc7..c19921dcdc61 100644 --- a/core/java/android/app/contextualsearch/flags.aconfig +++ b/core/java/android/app/contextualsearch/flags.aconfig @@ -8,6 +8,7 @@ flag { bug: "309689654" is_exported: true } + flag { name: "enable_token_refresh" namespace: "machine_learning" @@ -27,4 +28,11 @@ flag { namespace: "sysui_integrations" description: "Identify live contextual search UI to exclude from contextual search screenshot." bug: "372510690" +} + +flag { + name: "include_audio_playing_status" + namespace: "sysui_integrations" + description: "Add audio playing status to the contextual search invocation intent." + bug: "372935419" }
\ No newline at end of file diff --git a/core/java/android/app/jank/AppJankStats.java b/core/java/android/app/jank/AppJankStats.java index eea1d2ba5b9e..6ef6a44ddfbb 100644 --- a/core/java/android/app/jank/AppJankStats.java +++ b/core/java/android/app/jank/AppJankStats.java @@ -41,7 +41,8 @@ public final class AppJankStats { // The id that has been set for the widget. private String mWidgetId; - // A general category that the widget applies to. + // A general category the widget falls into based on the functions it performs or helps + // facilitate. private String mWidgetCategory; // The states that the UI elements can report @@ -53,78 +54,78 @@ public final class AppJankStats { // Total number of frames determined to be janky during the reported state. private long mJankyFrames; - // Histogram of frame duration overruns encoded in predetermined buckets. - private FrameOverrunHistogram mFrameOverrunHistogram; + // Histogram of relative frame times encoded in predetermined buckets. + private RelativeFrameTimeHistogram mRelativeFrameTimeHistogram; /** Used to indicate no widget category has been set. */ - public static final String WIDGET_CATEGORY_UNSPECIFIED = - "widget_category_unspecified"; + public static final String WIDGET_CATEGORY_UNSPECIFIED = "unspecified"; /** UI elements that facilitate scrolling. */ - public static final String SCROLL = "scroll"; + public static final String WIDGET_CATEGORY_SCROLL = "scroll"; /** UI elements that facilitate playing animations. */ - public static final String ANIMATION = "animation"; + public static final String WIDGET_CATEGORY_ANIMATION = "animation"; /** UI elements that facilitate media playback. */ - public static final String MEDIA = "media"; + public static final String WIDGET_CATEGORY_MEDIA = "media"; /** UI elements that facilitate in-app navigation. */ - public static final String NAVIGATION = "navigation"; + public static final String WIDGET_CATEGORY_NAVIGATION = "navigation"; /** UI elements that facilitate displaying, hiding or interacting with keyboard. */ - public static final String KEYBOARD = "keyboard"; - - /** UI elements that facilitate predictive back gesture navigation. */ - public static final String PREDICTIVE_BACK = "predictive_back"; + public static final String WIDGET_CATEGORY_KEYBOARD = "keyboard"; /** UI elements that don't fall in one or any of the other categories. */ - public static final String OTHER = "other"; + public static final String WIDGET_CATEGORY_OTHER = "other"; /** Used to indicate no widget state has been set. */ - public static final String WIDGET_STATE_UNSPECIFIED = "widget_state_unspecified"; + public static final String WIDGET_STATE_UNSPECIFIED = "unspecified"; /** Used to indicate the UI element currently has no state and is idle. */ - public static final String NONE = "none"; + public static final String WIDGET_STATE_NONE = "none"; /** Used to indicate the UI element is currently scrolling. */ - public static final String SCROLLING = "scrolling"; + public static final String WIDGET_STATE_SCROLLING = "scrolling"; /** Used to indicate the UI element is currently being flung. */ - public static final String FLINGING = "flinging"; + public static final String WIDGET_STATE_FLINGING = "flinging"; /** Used to indicate the UI element is currently being swiped. */ - public static final String SWIPING = "swiping"; + public static final String WIDGET_STATE_SWIPING = "swiping"; /** Used to indicate the UI element is currently being dragged. */ - public static final String DRAGGING = "dragging"; + public static final String WIDGET_STATE_DRAGGING = "dragging"; /** Used to indicate the UI element is currently zooming. */ - public static final String ZOOMING = "zooming"; + public static final String WIDGET_STATE_ZOOMING = "zooming"; /** Used to indicate the UI element is currently animating. */ - public static final String ANIMATING = "animating"; + public static final String WIDGET_STATE_ANIMATING = "animating"; /** Used to indicate the UI element is currently playing media. */ - public static final String PLAYBACK = "playback"; + public static final String WIDGET_STATE_PLAYBACK = "playback"; /** Used to indicate the UI element is currently being tapped on, for example on a keyboard. */ - public static final String TAPPING = "tapping"; + public static final String WIDGET_STATE_TAPPING = "tapping"; + + /** Used to indicate predictive back navigation is currently being used */ + public static final String WIDGET_STATE_PREDICTIVE_BACK = "predictive_back"; /** + * Provide an organized way to group widgets that have similar purposes or perform related + * functions. * @hide */ - @StringDef(value = { + @StringDef(prefix = {"WIDGET_CATEGORY_"}, value = { WIDGET_CATEGORY_UNSPECIFIED, - SCROLL, - ANIMATION, - MEDIA, - NAVIGATION, - KEYBOARD, - PREDICTIVE_BACK, - OTHER + WIDGET_CATEGORY_SCROLL, + WIDGET_CATEGORY_ANIMATION, + WIDGET_CATEGORY_MEDIA, + WIDGET_CATEGORY_NAVIGATION, + WIDGET_CATEGORY_KEYBOARD, + WIDGET_CATEGORY_OTHER }) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @Retention(RetentionPolicy.SOURCE) @@ -133,17 +134,18 @@ public final class AppJankStats { /** * @hide */ - @StringDef(value = { + @StringDef(prefix = {"WIDGET_STATE_"}, value = { WIDGET_STATE_UNSPECIFIED, - NONE, - SCROLLING, - FLINGING, - SWIPING, - DRAGGING, - ZOOMING, - ANIMATING, - PLAYBACK, - TAPPING, + WIDGET_STATE_NONE, + WIDGET_STATE_SCROLLING, + WIDGET_STATE_FLINGING, + WIDGET_STATE_SWIPING, + WIDGET_STATE_DRAGGING, + WIDGET_STATE_ZOOMING, + WIDGET_STATE_ANIMATING, + WIDGET_STATE_PLAYBACK, + WIDGET_STATE_TAPPING, + WIDGET_STATE_PREDICTIVE_BACK }) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @Retention(RetentionPolicy.SOURCE) @@ -156,31 +158,33 @@ public final class AppJankStats { * * @param appUid the Uid of the App that is collecting jank stats. * @param widgetId the widget id that frames will be associated to. - * @param widgetCategory a general functionality category that the widget falls into. Must be - * one of the following: SCROLL, ANIMATION, MEDIA, NAVIGATION, KEYBOARD, - * PREDICTIVE_BACK, OTHER or will be set to WIDGET_CATEGORY_UNSPECIFIED - * if no value is passed. - * @param widgetState the state the widget was in while frames were counted. Must be one of - * the following: NONE, SCROLLING, FLINGING, SWIPING, DRAGGING, ZOOMING, - * ANIMATING, PLAYBACK, TAPPING or will be set to WIDGET_STATE_UNSPECIFIED - * if no value is passed. + * @param widgetCategory a category used to organize widgets in a structured way that indicates + * they serve a similar purpose or perform related functions. Must be + * prefixed with WIDGET_CATEGORY_ and have a suffix of one of the + * following:SCROLL, ANIMATION, MEDIA, NAVIGATION, KEYBOARD, OTHER or + * will be set to UNSPECIFIED if no value is passed. + * @param widgetState the state the widget was in while frames were counted. Must be prefixed + * with WIDGET_STATE_ and have a suffix of one of the following: + * NONE, SCROLLING, FLINGING, SWIPING, DRAGGING, ZOOMING, ANIMATING, + * PLAYBACK, TAPPING, PREDICTIVE_BACK or will be set to + * WIDGET_STATE_UNSPECIFIED if no value is passed. * @param totalFrames the total number of frames that were counted for this stat. * @param jankyFrames the total number of janky frames that were counted for this stat. - * @param frameOverrunHistogram the histogram with predefined buckets. See - * {@link #getFrameOverrunHistogram()} for details. + * @param relativeFrameTimeHistogram the histogram with predefined buckets. See + * {@link #getRelativeFrameTimeHistogram()} for details. * */ public AppJankStats(int appUid, @NonNull String widgetId, @Nullable @WidgetCategory String widgetCategory, @Nullable @WidgetState String widgetState, long totalFrames, long jankyFrames, - @NonNull FrameOverrunHistogram frameOverrunHistogram) { + @NonNull RelativeFrameTimeHistogram relativeFrameTimeHistogram) { mUid = appUid; mWidgetId = widgetId; mWidgetCategory = widgetCategory != null ? widgetCategory : WIDGET_CATEGORY_UNSPECIFIED; mWidgetState = widgetState != null ? widgetState : WIDGET_STATE_UNSPECIFIED; mTotalFrames = totalFrames; mJankyFrames = jankyFrames; - mFrameOverrunHistogram = frameOverrunHistogram; + mRelativeFrameTimeHistogram = relativeFrameTimeHistogram; } /** @@ -203,7 +207,7 @@ public final class AppJankStats { /** * Returns the category that the widget's functionality generally falls into, or - * widget_category_unspecified {@link #WIDGET_CATEGORY_UNSPECIFIED} if no value was passed in. + * {@link #WIDGET_CATEGORY_UNSPECIFIED} if no value was passed in. * * @return the category that the widget's functionality generally falls into, this value cannot * be null. @@ -213,7 +217,7 @@ public final class AppJankStats { } /** - * Returns the widget's state that was reported for this stat, or widget_state_unspecified + * Returns the widget's state that was reported for this stat, or * {@link #WIDGET_STATE_UNSPECIFIED} if no value was passed in. * * @return the widget's state that was reported for this stat. This value cannot be null. @@ -241,13 +245,13 @@ public final class AppJankStats { } /** - * Returns a Histogram containing frame overrun times in millis grouped into predefined buckets. - * See {@link FrameOverrunHistogram} for more information. + * Returns a Histogram containing relative frame times in millis grouped into predefined + * buckets. See {@link RelativeFrameTimeHistogram} for more information. * - * @return Histogram containing frame overrun times in predefined buckets. This value cannot + * @return Histogram containing relative frame times in predefined buckets. This value cannot * be null. */ - public @NonNull FrameOverrunHistogram getFrameOverrunHistogram() { - return mFrameOverrunHistogram; + public @NonNull RelativeFrameTimeHistogram getRelativeFrameTimeHistogram() { + return mRelativeFrameTimeHistogram; } } diff --git a/core/java/android/app/jank/FrameOverrunHistogram.java b/core/java/android/app/jank/FrameOverrunHistogram.java deleted file mode 100644 index 3ad6531a46bf..000000000000 --- a/core/java/android/app/jank/FrameOverrunHistogram.java +++ /dev/null @@ -1,107 +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.jank; - -import android.annotation.FlaggedApi; -import android.annotation.NonNull; - -import java.util.Arrays; - -/** - * This class is intended to be used when reporting {@link AppJankStats} back to the system. It's - * intended to be used by library widgets to help facilitate the reporting of frame overrun times - * by adding those times into predefined buckets. - */ -@FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) -public class FrameOverrunHistogram { - private static int[] sBucketEndpoints = new int[]{ - Integer.MIN_VALUE, -200, -150, -100, -90, -80, -70, -60, -50, -40, -30, -25, -20, -18, - -16, -14, -12, -10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 25, 30, 40, - 50, 60, 70, 80, 90, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000 - }; - private int[] mBucketCounts; - - /** - * Create a new instance of FrameOverrunHistogram. - */ - public FrameOverrunHistogram() { - mBucketCounts = new int[sBucketEndpoints.length]; - } - - /** - * Increases the count by one for the bucket representing the frame overrun duration. - * - * @param frameOverrunMillis frame overrun duration in millis, frame overrun is the difference - * between a frames deadline and when it was rendered. - */ - public void addFrameOverrunMillis(int frameOverrunMillis) { - int countsIndex = getIndexForCountsFromOverrunTime(frameOverrunMillis); - mBucketCounts[countsIndex]++; - } - - /** - * Returns the counts for the all the frame overrun buckets. - * - * @return an array of integers representing the counts of frame overrun times. This value - * cannot be null. - */ - public @NonNull int[] getBucketCounters() { - return Arrays.copyOf(mBucketCounts, mBucketCounts.length); - } - - /** - * Returns the predefined endpoints for the histogram. - * - * @return array of integers representing the endpoints for the predefined histogram count - * buckets. This value cannot be null. - */ - public @NonNull int[] getBucketEndpointsMillis() { - return Arrays.copyOf(sBucketEndpoints, sBucketEndpoints.length); - } - - // This takes the overrun time and returns what bucket it belongs to in the counters array. - private int getIndexForCountsFromOverrunTime(int overrunTime) { - if (overrunTime < 20) { - if (overrunTime >= -20) { - return (overrunTime + 20) / 2 + 12; - } - if (overrunTime >= -30) { - return (overrunTime + 30) / 5 + 10; - } - if (overrunTime >= -100) { - return (overrunTime + 100) / 10 + 3; - } - if (overrunTime >= -200) { - return (overrunTime + 200) / 50 + 1; - } - return 0; - } - if (overrunTime < 30) { - return (overrunTime - 20) / 5 + 32; - } - if (overrunTime < 100) { - return (overrunTime - 30) / 10 + 34; - } - if (overrunTime < 200) { - return (overrunTime - 50) / 100 + 41; - } - if (overrunTime < 1000) { - return (overrunTime - 200) / 100 + 43; - } - return sBucketEndpoints.length - 1; - } -} diff --git a/core/java/android/app/jank/JankDataProcessor.java b/core/java/android/app/jank/JankDataProcessor.java index c9472598b352..b4c293eeb695 100644 --- a/core/java/android/app/jank/JankDataProcessor.java +++ b/core/java/android/app/jank/JankDataProcessor.java @@ -111,7 +111,7 @@ public class JankDataProcessor { pendingStat.mTotalFrames += jankStat.getTotalFrameCount(); mergeOverrunHistograms(pendingStat.mFrameOverrunBuckets, - jankStat.getFrameOverrunHistogram().getBucketCounters()); + jankStat.getRelativeFrameTimeHistogram().getBucketCounters()); } private void mergeNewStat(String stateKey, String activityName, AppJankStats jankStats) { @@ -136,7 +136,7 @@ public class JankDataProcessor { pendingStat.mJankyFrames = jankStats.getJankyFrameCount(); mergeOverrunHistograms(pendingStat.mFrameOverrunBuckets, - jankStats.getFrameOverrunHistogram().getBucketCounters()); + jankStats.getRelativeFrameTimeHistogram().getBucketCounters()); mPendingJankStats.put(stateKey, pendingStat); } @@ -271,7 +271,8 @@ public class JankDataProcessor { private static final int[] sFrameOverrunHistogramBounds = { Integer.MIN_VALUE, -200, -150, -100, -90, -80, -70, -60, -50, -40, -30, -25, -20, -18, -16, -14, -12, -10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 25, - 30, 40, 50, 60, 70, 80, 90, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000 + 30, 40, 50, 60, 70, 80, 90, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000, + Integer.MAX_VALUE }; private final int[] mFrameOverrunBuckets = new int[sFrameOverrunHistogramBounds.length]; @@ -414,7 +415,7 @@ public class JankDataProcessor { if (overrunTime < 200) { return (overrunTime - 50) / 100 + 41; } - if (overrunTime < 1000) { + if (overrunTime <= 1000) { return (overrunTime - 200) / 100 + 43; } return sFrameOverrunHistogramBounds.length - 1; diff --git a/core/java/android/app/jank/RelativeFrameTimeHistogram.java b/core/java/android/app/jank/RelativeFrameTimeHistogram.java new file mode 100644 index 000000000000..666f90f89f45 --- /dev/null +++ b/core/java/android/app/jank/RelativeFrameTimeHistogram.java @@ -0,0 +1,129 @@ +/* + * 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.jank; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; + +import java.util.Arrays; + +/** + * A histogram of frame times relative to their deadline. + * + * This class aids in reporting {@link AppJankStats} to the system and is designed for use by + * library widgets. It facilitates the recording of frame times in relation to the frame deadline. + * The class records the distribution of time remaining until a frame is considered janky or how + * janky the frame was. + * <p> + * A frame's relative frame time value indicates whether it was delivered early, on time, or late. + * A negative relative frame time value indicates the frame was delivered early, a value of zero + * indicates the frame was delivered on time and a positive value indicates the frame was delivered + * late. The values of the endpoints indicate how early or late a frame was delivered. + * <p> + * The relative frame times are recorded as a histogram: values are + * {@link #addRelativeFrameTimeMillis added} to a bucket by increasing the bucket's counter. The + * count of frames with a relative frame time between + * {@link #getBucketEndpointsMillis bucket endpoints} {@code i} and {@code i+1} can be obtained + * through index {@code i} of {@link #getBucketCounters}. + * + */ +@FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) +public class RelativeFrameTimeHistogram { + private static int[] sBucketEndpoints = new int[]{ + Integer.MIN_VALUE, -200, -150, -100, -90, -80, -70, -60, -50, -40, -30, -25, -20, -18, + -16, -14, -12, -10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 25, 30, 40, + 50, 60, 70, 80, 90, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000, + Integer.MAX_VALUE + }; + // + private int[] mBucketCounts; + + /** + * Create a new instance of RelativeFrameTimeHistogram. + */ + public RelativeFrameTimeHistogram() { + mBucketCounts = new int[sBucketEndpoints.length - 1]; + } + + /** + * Increases the count by one for the bucket representing the relative frame time. + * + * @param frameTimeMillis relative frame time in millis, relative frame time is the difference + * between a frames deadline and when it was rendered. + */ + public void addRelativeFrameTimeMillis(int frameTimeMillis) { + int countsIndex = getRelativeFrameTimeBucketIndex(frameTimeMillis); + mBucketCounts[countsIndex]++; + } + + /** + * Returns the counts for the all the relative frame time buckets. + * + * @return an array of integers representing the counts of relative frame times. This value + * cannot be null. + */ + public @NonNull int[] getBucketCounters() { + return Arrays.copyOf(mBucketCounts, mBucketCounts.length); + } + + /** + * Returns the relative frame time endpoints for the histogram. + * <p> + * Index {@code i} of {@link #getBucketCounters} contains the count of frames that had a + * relative frame time between {@code endpoints[i]} (inclusive) and {@code endpoints[i+1]} + * (exclusive). + * + * @return array of integers representing the endpoints for the predefined histogram count + * buckets. This value cannot be null. + */ + public @NonNull int[] getBucketEndpointsMillis() { + return Arrays.copyOf(sBucketEndpoints, sBucketEndpoints.length); + } + + // This takes the relative frame time and returns what bucket it belongs to in the counters + // array. + private int getRelativeFrameTimeBucketIndex(int relativeFrameTime) { + if (relativeFrameTime < 20) { + if (relativeFrameTime >= -20) { + return (relativeFrameTime + 20) / 2 + 12; + } + if (relativeFrameTime >= -30) { + return (relativeFrameTime + 30) / 5 + 10; + } + if (relativeFrameTime >= -100) { + return (relativeFrameTime + 100) / 10 + 3; + } + if (relativeFrameTime >= -200) { + return (relativeFrameTime + 200) / 50 + 1; + } + return 0; + } + if (relativeFrameTime < 30) { + return (relativeFrameTime - 20) / 5 + 32; + } + if (relativeFrameTime < 100) { + return (relativeFrameTime - 30) / 10 + 34; + } + if (relativeFrameTime < 200) { + return (relativeFrameTime - 50) / 100 + 41; + } + if (relativeFrameTime < 1000) { + return (relativeFrameTime - 200) / 100 + 43; + } + return mBucketCounts.length - 1; + } +} diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index 0d219a901b9d..4c4753872c03 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -152,7 +152,7 @@ flag { name: "cache_sdk_system_features" namespace: "system_performance" description: "Feature flag to enable optimized cache for SDK-defined system feature lookups." - bug: "375000483" + bug: "326623529" } flag { diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 852f04793f15..9c6b71b72ec8 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -417,6 +417,7 @@ public abstract class CameraDevice implements AutoCloseable { * or if any of the output configurations sets a stream use * case different from {@link * android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT}. + * @throws UnsupportedOperationException if the camera has been opened in shared mode * @see CameraExtensionCharacteristics#getSupportedExtensions * @see CameraExtensionCharacteristics#getExtensionSupportedSizes */ @@ -1258,7 +1259,8 @@ public abstract class CameraDevice implements AutoCloseable { * configurations are empty; or the session configuration * executor is invalid; * or the output dynamic range combination is - * invalid/unsupported. + * invalid/unsupported; or the session type is not shared when + * camera has been opened in shared mode. * @throws CameraAccessException In case the camera device is no longer connected or has * encountered a fatal error. * @see #createCaptureSession(List, CameraCaptureSession.StateCallback, Handler) @@ -1292,6 +1294,8 @@ public abstract class CameraDevice implements AutoCloseable { * @throws CameraAccessException if the camera device is no longer connected or has * encountered a fatal error * @throws IllegalStateException if the camera device has been closed + * @throws UnsupportedOperationException if this is not a primary client of a camera opened in + * shared mode */ @NonNull public abstract CaptureRequest.Builder createCaptureRequest(@RequestTemplate int templateType) @@ -1328,6 +1332,8 @@ public abstract class CameraDevice implements AutoCloseable { * @throws CameraAccessException if the camera device is no longer connected or has * encountered a fatal error * @throws IllegalStateException if the camera device has been closed + * @throws UnsupportedOperationException if this is not a primary client of a camera opened in + * shared mode * * @see #TEMPLATE_PREVIEW * @see #TEMPLATE_RECORD @@ -1369,6 +1375,7 @@ public abstract class CameraDevice implements AutoCloseable { * @throws CameraAccessException if the camera device is no longer connected or has * encountered a fatal error * @throws IllegalStateException if the camera device has been closed + * @throws UnsupportedOperationException if the camera has been opened in shared mode * * @see CaptureRequest.Builder * @see TotalCaptureResult diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index aba2345f28d8..bfaff941939c 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -1375,6 +1375,9 @@ public final class CameraManager { * @throws SecurityException if the application does not have permission to * access the camera * + * @throws UnsupportedOperationException if {@link #isCameraDeviceSharingSupported} returns + * false for the given {@code cameraId}. + * * @see #getCameraIdList * @see android.app.admin.DevicePolicyManager#setCameraDisabled * @@ -1393,6 +1396,10 @@ public final class CameraManager { if (executor == null) { throw new IllegalArgumentException("executor was null"); } + if (!isCameraDeviceSharingSupported(cameraId)) { + throw new UnsupportedOperationException( + "CameraDevice sharing is not supported for Camera ID: " + cameraId); + } openCameraImpl(cameraId, callback, executor, /*oomScoreOffset*/0, getRotationOverride(mContext), /*sharedMode*/true); } diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 496d316eb028..1c65b0882e0f 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -299,6 +299,24 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> return mRequestType; } + /** + * Get the stream ids corresponding to the target surfaces. + * + * @hide + */ + public int[] getStreamIds() { + return mStreamIdxArray; + }; + + /** + * Get the surface ids corresponding to the target surfaces. + * + * @hide + */ + public int[] getSurfaceIds() { + return mSurfaceIdxArray; + }; + // If this request is part of constrained high speed request list that was created by // {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList} private boolean mIsPartOfCHSRequestList = false; diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java index ce8661e90978..7e0456b22be8 100644 --- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java @@ -340,6 +340,30 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession } } + /** + * Shared Camera capture session API which can be used by the clients + * to start streaming. + * + * @hide + */ + public int startStreaming(List<Surface> surfaces, Executor executor, + CaptureCallback callback) throws CameraAccessException { + + synchronized (mDeviceImpl.mInterfaceLock) { + checkNotClosed(); + + executor = CameraDeviceImpl.checkExecutor(executor, callback); + + if (DEBUG) { + Log.v(TAG, mIdString + "startStreaming callback " + callback + " executor" + + " " + executor); + } + + return addPendingSequence(mDeviceImpl.startStreaming(surfaces, + createCaptureCallbackProxyWithExecutor(executor, callback), mDeviceExecutor)); + } + } + private void checkRepeatingRequest(CaptureRequest request) { if (request == null) { throw new IllegalArgumentException("request must not be null"); diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 34c0f7b19da9..89a6b02b56c4 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -451,6 +451,16 @@ public class CameraDeviceImpl extends CameraDevice } } + /** + * When camera device is opened in shared mode, call to check if this is a primary client. + * + */ + public boolean isPrimaryClient() { + synchronized (mInterfaceLock) { + return mIsPrimaryClient; + } + } + private Map<String, CameraCharacteristics> getPhysicalIdToChars() { if (mPhysicalIdsToChars == null) { try { @@ -482,8 +492,19 @@ public class CameraDeviceImpl extends CameraDevice mRemoteDevice = new ICameraDeviceUserWrapper(remoteDevice); Parcel resultParcel = Parcel.obtain(); - mRemoteDevice.getCaptureResultMetadataQueue().writeToParcel(resultParcel, 0); + + // Passing in PARCELABLE_WRITE_RETURN_VALUE closes the ParcelFileDescriptors + // owned by MQDescriptor returned by getCaptureResultMetadataQueue() + // Though these will be closed when GC runs, that may not happen for a while. + // Also, apps running with StrictMode would get warnings / crash in the case they're not + // explicitly closed. + mRemoteDevice.getCaptureResultMetadataQueue().writeToParcel(resultParcel, + Parcelable.PARCELABLE_WRITE_RETURN_VALUE); mFMQReader = nativeCreateFMQReader(resultParcel); + // Recycle since resultParcel would dup fds from MQDescriptor as well. We don't + // need them after the native FMQ reader has been created. That is since the native + // creates calls MQDescriptor.readFromParcel() which again dups the fds. + resultParcel.recycle(); IBinder remoteDeviceBinder = remoteDevice.asBinder(); // For legacy camera device, remoteDevice is in the same process, and @@ -847,24 +868,19 @@ public class CameraDeviceImpl extends CameraDevice List<SharedOutputConfiguration> sharedConfigs = sharedSessionConfiguration.getOutputStreamsInformation(); for (SharedOutputConfiguration sharedConfig : sharedConfigs) { - if (outConfig.getConfiguredSize().equals(sharedConfig.getSize()) - && (outConfig.getConfiguredFormat() == sharedConfig.getFormat()) - && (outConfig.getSurfaceGroupId() == OutputConfiguration.SURFACE_GROUP_ID_NONE) - && (outConfig.getSurfaceType() == sharedConfig.getSurfaceType()) + if ((outConfig.getSurfaceGroupId() == OutputConfiguration.SURFACE_GROUP_ID_NONE) && (outConfig.getMirrorMode() == sharedConfig.getMirrorMode()) - && (outConfig.getUsage() == sharedConfig.getUsage()) && (outConfig.isReadoutTimestampEnabled() == sharedConfig.isReadoutTimestampEnabled()) && (outConfig.getTimestampBase() == sharedConfig.getTimestampBase()) && (outConfig.getStreamUseCase() == sharedConfig.getStreamUseCase()) - && (outConfig.getColorSpace().equals( - sharedSessionConfiguration.getColorSpace())) && (outConfig.getDynamicRangeProfile() == DynamicRangeProfiles.STANDARD) - && (outConfig.getConfiguredDataspace() == sharedConfig.getDataspace()) && (Objects.equals(outConfig.getPhysicalCameraId(), sharedConfig.getPhysicalCameraId())) && (outConfig.getSensorPixelModes().isEmpty()) + && (!outConfig.isMultiResolution()) + && (!outConfig.isDeferredConfiguration()) && (!outConfig.isShared())) { //Found valid config, return true return true; @@ -896,14 +912,6 @@ public class CameraDeviceImpl extends CameraDevice if (config.getExecutor() == null) { throw new IllegalArgumentException("Invalid executor"); } - if (mSharedMode) { - if (config.getSessionType() != SessionConfiguration.SESSION_SHARED) { - throw new IllegalArgumentException("Invalid session type"); - } - if (!checkSharedSessionConfiguration(outputConfigs)) { - throw new IllegalArgumentException("Invalid output configurations"); - } - } createCaptureSessionInternal(config.getInputConfiguration(), outputConfigs, config.getStateCallback(), config.getExecutor(), config.getSessionType(), config.getSessionParameters()); @@ -921,17 +929,26 @@ public class CameraDeviceImpl extends CameraDevice checkIfCameraClosedOrInError(); + boolean isSharedSession = (operatingMode == ICameraDeviceUser.SHARED_MODE); + if (Flags.cameraMultiClient() && mSharedMode) { + if (!isSharedSession) { + throw new IllegalArgumentException("Invalid session type"); + } + if (!checkSharedSessionConfiguration(outputConfigurations)) { + throw new IllegalArgumentException("Invalid output configurations"); + } + if (inputConfig != null) { + throw new IllegalArgumentException("Shared capture session doesn't support" + + " input configuration yet."); + } + } + boolean isConstrainedHighSpeed = (operatingMode == ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE); if (isConstrainedHighSpeed && inputConfig != null) { throw new IllegalArgumentException("Constrained high speed session doesn't support" + " input configuration yet."); } - boolean isSharedSession = (operatingMode == ICameraDeviceUser.SHARED_MODE); - if (isSharedSession && inputConfig != null) { - throw new IllegalArgumentException("Shared capture session doesn't support" - + " input configuration yet."); - } if (mCurrentExtensionSession != null) { mCurrentExtensionSession.commitStats(); @@ -993,8 +1010,7 @@ public class CameraDeviceImpl extends CameraDevice mCharacteristics); } else if (isSharedSession) { newSession = new CameraSharedCaptureSessionImpl(mNextSessionId++, - callback, executor, this, mDeviceExecutor, configureSuccess, - mIsPrimaryClient); + callback, executor, this, mDeviceExecutor, configureSuccess); } else { newSession = new CameraCaptureSessionImpl(mNextSessionId++, input, callback, executor, this, mDeviceExecutor, configureSuccess); @@ -1063,6 +1079,11 @@ public class CameraDeviceImpl extends CameraDevice synchronized(mInterfaceLock) { checkIfCameraClosedOrInError(); + if (Flags.cameraMultiClient() && mSharedMode && !mIsPrimaryClient) { + throw new UnsupportedOperationException("In shared session mode," + + "only primary clients can create capture request."); + } + for (String physicalId : physicalCameraIdSet) { if (Objects.equals(physicalId, getId())) { throw new IllegalStateException("Physical id matches the logical id!"); @@ -1089,6 +1110,11 @@ public class CameraDeviceImpl extends CameraDevice synchronized(mInterfaceLock) { checkIfCameraClosedOrInError(); + if (Flags.cameraMultiClient() && mSharedMode && !mIsPrimaryClient) { + throw new UnsupportedOperationException("In shared session mode," + + "only primary clients can create capture request."); + } + CameraMetadataNative templatedRequest = null; templatedRequest = mRemoteDevice.createDefaultRequest(templateType); @@ -1108,6 +1134,10 @@ public class CameraDeviceImpl extends CameraDevice throws CameraAccessException { synchronized(mInterfaceLock) { checkIfCameraClosedOrInError(); + if (Flags.cameraMultiClient() && mSharedMode) { + throw new UnsupportedOperationException("In shared session mode," + + "reprocess capture requests are not supported."); + } CameraMetadataNative resultMetadata = new CameraMetadataNative(inputResult.getNativeCopy()); @@ -1561,6 +1591,74 @@ public class CameraDeviceImpl extends CameraDevice } } + public int startStreaming(List<Surface> surfaces, CaptureCallback callback, + Executor executor) throws CameraAccessException { + // Need a valid executor, or current thread needs to have a looper, if + // callback is valid + executor = checkExecutor(executor, callback); + synchronized (mInterfaceLock) { + checkIfCameraClosedOrInError(); + for (Surface surface : surfaces) { + if (surface == null) { + throw new IllegalArgumentException("Null Surface targets are not allowed"); + } + } + // In shared session mode, if there are other active clients streaming then + // stoprepeating does not actually send request to HAL to cancel the request. + // Cameraservice will use this call to remove this client surfaces provided in its + // previous streaming request. If this is the only client for the shared camera device + // then camerservice will ask HAL to cancel the previous repeating request + stopRepeating(); + + // StartStreaming API does not allow capture parameters to be provided through a capture + // request. If the primary client has an existing repeating request, the camera service + // will either attach the provided surfaces to that request or create a default capture + // request if no repeating request is active. A default capture request is created here + // for initial use. The capture callback will provide capture results that include the + // actual capture parameters used for the streaming. + CaptureRequest.Builder builder = createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); + for (Surface surface : surfaces) { + builder.addTarget(surface); + } + CaptureRequest request = builder.build(); + request.convertSurfaceToStreamId(mConfiguredOutputs); + + SubmitInfo requestInfo; + requestInfo = mRemoteDevice.startStreaming(request.getStreamIds(), + request.getSurfaceIds()); + request.recoverStreamIdToSurface(); + List<CaptureRequest> requestList = new ArrayList<CaptureRequest>(); + requestList.add(request); + + if (callback != null) { + mCaptureCallbackMap.put(requestInfo.getRequestId(), + new CaptureCallbackHolder( + callback, requestList, executor, true, mNextSessionId - 1)); + } else { + if (DEBUG) { + Log.d(TAG, "Listen for request " + requestInfo.getRequestId() + " is null"); + } + } + + if (mRepeatingRequestId != REQUEST_ID_NONE) { + checkEarlyTriggerSequenceCompleteLocked(mRepeatingRequestId, + requestInfo.getLastFrameNumber(), mRepeatingRequestTypes); + } + + CaptureRequest[] requestArray = requestList.toArray( + new CaptureRequest[requestList.size()]); + mRepeatingRequestId = requestInfo.getRequestId(); + mRepeatingRequestTypes = getRequestTypes(requestArray); + + if (mIdle) { + mDeviceExecutor.execute(mCallOnActive); + } + mIdle = false; + + return requestInfo.getRequestId(); + } + } + public int setRepeatingRequest(CaptureRequest request, CaptureCallback callback, Executor executor) throws CameraAccessException { List<CaptureRequest> requestList = new ArrayList<CaptureRequest>(); @@ -2883,6 +2981,11 @@ public class CameraDeviceImpl extends CameraDevice @Override public void createExtensionSession(ExtensionSessionConfiguration extensionConfiguration) throws CameraAccessException { + if (Flags.cameraMultiClient() && mSharedMode) { + throw new UnsupportedOperationException("In shared session mode," + + "extension sessions are not supported."); + } + HashMap<String, CameraCharacteristics> characteristicsMap = new HashMap<>( getPhysicalIdToChars()); characteristicsMap.put(mCameraId, mCharacteristics); @@ -2918,4 +3021,4 @@ public class CameraDeviceImpl extends CameraDevice } } } -}
\ No newline at end of file +} diff --git a/core/java/android/hardware/camera2/impl/CameraSharedCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraSharedCaptureSessionImpl.java index a1f31c0ced5e..8c0dcfb2a28c 100644 --- a/core/java/android/hardware/camera2/impl/CameraSharedCaptureSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraSharedCaptureSessionImpl.java @@ -19,6 +19,8 @@ import android.annotation.FlaggedApi; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.CameraOfflineSession; +import android.hardware.camera2.CameraOfflineSession.CameraOfflineSessionCallback; import android.hardware.camera2.CameraSharedCaptureSession; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.params.OutputConfiguration; @@ -28,6 +30,7 @@ import android.view.Surface; import com.android.internal.camera.flags.Flags; +import java.util.Collection; import java.util.List; import java.util.concurrent.Executor; @@ -46,7 +49,8 @@ public class CameraSharedCaptureSessionImpl private static final String TAG = "CameraSharedCaptureSessionImpl"; private final CameraCaptureSessionImpl mSessionImpl; private final ConditionVariable mInitialized = new ConditionVariable(); - private boolean mIsPrimary; + private final android.hardware.camera2.impl.CameraDeviceImpl mCameraDevice; + private final Executor mDeviceExecutor; /** * Create a new CameraCaptureSession. @@ -54,24 +58,32 @@ public class CameraSharedCaptureSessionImpl CameraSharedCaptureSessionImpl(int id, CameraCaptureSession.StateCallback callback, Executor stateExecutor, android.hardware.camera2.impl.CameraDeviceImpl deviceImpl, - Executor deviceStateExecutor, boolean configureSuccess, boolean isPrimary) { + Executor deviceStateExecutor, boolean configureSuccess) { CameraCaptureSession.StateCallback wrapperCallback = new WrapperCallback(callback); mSessionImpl = new CameraCaptureSessionImpl(id, /*input*/null, wrapperCallback, stateExecutor, deviceImpl, deviceStateExecutor, configureSuccess); - mIsPrimary = isPrimary; + mCameraDevice = deviceImpl; + mDeviceExecutor = deviceStateExecutor; mInitialized.open(); } @Override - public int startStreaming(List<Surface> surfaces, Executor executor, CaptureCallback listener) + public int startStreaming(List<Surface> surfaces, Executor executor, CaptureCallback callback) throws CameraAccessException { - // Todo: Need to add implementation. - return 0; + if (surfaces.isEmpty()) { + throw new IllegalArgumentException("No surfaces provided for streaming"); + } else if (executor == null) { + throw new IllegalArgumentException("executor must not be null"); + } else if (callback == null) { + throw new IllegalArgumentException("callback must not be null"); + } + + return mSessionImpl.startStreaming(surfaces, executor, callback); } @Override public void stopStreaming() throws CameraAccessException { - // Todo: Need to add implementation. + mSessionImpl.stopRepeating(); } @Override @@ -90,16 +102,24 @@ public class CameraSharedCaptureSessionImpl } @Override + public boolean supportsOfflineProcessing(Surface surface) { + return false; + } + + @Override public void abortCaptures() throws CameraAccessException { - if (mIsPrimary) { + if (mCameraDevice.isPrimaryClient()) { mSessionImpl.abortCaptures(); + return; } + throw new UnsupportedOperationException("Shared capture session only supports this method" + + " for primary clients"); } @Override public int setRepeatingRequest(CaptureRequest request, CaptureCallback listener, Handler handler) throws CameraAccessException { - if (mIsPrimary) { + if (mCameraDevice.isPrimaryClient()) { return mSessionImpl.setRepeatingRequest(request, listener, handler); } throw new UnsupportedOperationException("Shared capture session only supports this method" @@ -107,16 +127,30 @@ public class CameraSharedCaptureSessionImpl } @Override + public int setSingleRepeatingRequest(CaptureRequest request, Executor executor, + CaptureCallback listener) + throws CameraAccessException { + if (mCameraDevice.isPrimaryClient()) { + return mSessionImpl.setSingleRepeatingRequest(request, executor, listener); + } + throw new UnsupportedOperationException("Shared capture session only supports this method" + + " for primary clients"); + } + + @Override public void stopRepeating() throws CameraAccessException { - if (mIsPrimary) { + if (mCameraDevice.isPrimaryClient()) { mSessionImpl.stopRepeating(); + return; } + throw new UnsupportedOperationException("Shared capture session only supports this method" + + " for primary clients"); } @Override public int capture(CaptureRequest request, CaptureCallback listener, Handler handler) throws CameraAccessException { - if (mIsPrimary) { + if (mCameraDevice.isPrimaryClient()) { return mSessionImpl.capture(request, listener, handler); } throw new UnsupportedOperationException("Shared capture session only supports this method" @@ -124,6 +158,17 @@ public class CameraSharedCaptureSessionImpl } @Override + public int captureSingleRequest(CaptureRequest request, Executor executor, + CaptureCallback listener) + throws CameraAccessException { + if (mCameraDevice.isPrimaryClient()) { + return mSessionImpl.captureSingleRequest(request, executor, listener); + } + throw new UnsupportedOperationException("Shared capture session only supports this method" + + " for primary clients"); + } + + @Override public void tearDown(Surface surface) throws CameraAccessException { mSessionImpl.tearDown(surface); } @@ -149,48 +194,72 @@ public class CameraSharedCaptureSessionImpl } @Override + public CameraOfflineSession switchToOffline(Collection<Surface> offlineSurfaces, + Executor executor, CameraOfflineSessionCallback listener) + throws CameraAccessException { + throw new UnsupportedOperationException("Shared capture session do not support this method" + ); + } + + @Override public int setRepeatingBurst(List<CaptureRequest> requests, CaptureCallback listener, Handler handler) throws CameraAccessException { - throw new UnsupportedOperationException("Shared Capture session doesn't support" + throw new UnsupportedOperationException("Shared Capture session do not support" + + " this method"); + } + + @Override + public int setRepeatingBurstRequests(List<CaptureRequest> requests, + Executor executor, CaptureCallback listener) + throws CameraAccessException { + throw new UnsupportedOperationException("Shared Capture session do not support" + " this method"); } @Override public int captureBurst(List<CaptureRequest> requests, CaptureCallback listener, Handler handler) throws CameraAccessException { - throw new UnsupportedOperationException("Shared Capture session doesn't support" + throw new UnsupportedOperationException("Shared Capture session do not support" + + " this method"); + } + + @Override + public int captureBurstRequests(List<CaptureRequest> requests, + Executor executor, CaptureCallback listener) + throws CameraAccessException { + throw new UnsupportedOperationException("Shared Capture session do not support" + " this method"); } @Override public void updateOutputConfiguration(OutputConfiguration config) throws CameraAccessException { - throw new UnsupportedOperationException("Shared capture session doesn't support" + throw new UnsupportedOperationException("Shared capture session do not support" + " this method"); } @Override public void finalizeOutputConfigurations(List<OutputConfiguration> deferredOutputConfigs) throws CameraAccessException { - throw new UnsupportedOperationException("Shared capture session doesn't support" + throw new UnsupportedOperationException("Shared capture session do not support" + " this method"); } @Override public void prepare(Surface surface) throws CameraAccessException { - throw new UnsupportedOperationException("Shared capture session doesn't support" + throw new UnsupportedOperationException("Shared capture session do not support" + " this method"); } @Override public void prepare(int maxCount, Surface surface) throws CameraAccessException { - throw new UnsupportedOperationException("Shared capture session doesn't support" + throw new UnsupportedOperationException("Shared capture session do not support" + " this method"); } @Override public void closeWithoutDraining() { - throw new UnsupportedOperationException("Shared capture session doesn't support" + throw new UnsupportedOperationException("Shared capture session do not support" + " this method"); } diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java index a79e084b7f41..0b8e9c2687c3 100644 --- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java +++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java @@ -65,6 +65,17 @@ public class ICameraDeviceUserWrapper { } } + public SubmitInfo startStreaming(int[] streamIdxArray, int[] surfaceIdxArray) + throws CameraAccessException { + try { + return mRemoteDevice.startStreaming(streamIdxArray, surfaceIdxArray); + } catch (ServiceSpecificException e) { + throw ExceptionUtils.throwAsPublicException(e); + } catch (RemoteException e) { + throw ExceptionUtils.throwAsPublicException(e); + } + } + public SubmitInfo submitRequest(CaptureRequest request, boolean streaming) throws CameraAccessException { try { @@ -325,4 +336,4 @@ public class ICameraDeviceUserWrapper { } } -}
\ No newline at end of file +} diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index e12c46322d8c..d394154a2c0e 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -1803,6 +1803,19 @@ public final class OutputConfiguration implements Parcelable { } /** + * Get the flag indicating if this {@link OutputConfiguration} is for a multi-resolution output + * with a MultiResolutionImageReader. + * + * @return true if this {@link OutputConfiguration} is for a multi-resolution output with a + * MultiResolutionImageReader. + * + * @hide + */ + public boolean isMultiResolution() { + return mIsMultiResolution; + } + + /** * Get the physical camera ID associated with this {@link OutputConfiguration}. * * <p>If this OutputConfiguration isn't targeting a physical camera of a logical diff --git a/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java b/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java index cdcc92ce4404..365f870ba22d 100644 --- a/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java +++ b/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java @@ -212,7 +212,7 @@ public final class SharedSessionConfiguration { } public @Nullable String getPhysicalCameraId() { - return mPhysicalCameraId; + return mPhysicalCameraId.isEmpty() ? null : mPhysicalCameraId; } } diff --git a/core/java/android/hardware/contexthub/HubEndpoint.java b/core/java/android/hardware/contexthub/HubEndpoint.java index 99f331f43450..de88895ba55c 100644 --- a/core/java/android/hardware/contexthub/HubEndpoint.java +++ b/core/java/android/hardware/contexthub/HubEndpoint.java @@ -99,8 +99,8 @@ public class HubEndpoint { private final Object mLock = new Object(); private final HubEndpointInfo mPendingHubEndpointInfo; - @Nullable private final IHubEndpointLifecycleCallback mLifecycleCallback; - @Nullable private final IHubEndpointMessageCallback mMessageCallback; + @Nullable private final HubEndpointLifecycleCallback mLifecycleCallback; + @Nullable private final HubEndpointMessageCallback mMessageCallback; @NonNull private final Executor mLifecycleCallbackExecutor; @NonNull private final Executor mMessageCallbackExecutor; @@ -335,9 +335,9 @@ public class HubEndpoint { private HubEndpoint( @NonNull HubEndpointInfo pendingEndpointInfo, - @Nullable IHubEndpointLifecycleCallback endpointLifecycleCallback, + @Nullable HubEndpointLifecycleCallback endpointLifecycleCallback, @NonNull Executor lifecycleCallbackExecutor, - @Nullable IHubEndpointMessageCallback endpointMessageCallback, + @Nullable HubEndpointMessageCallback endpointMessageCallback, @NonNull Executor messageCallbackExecutor) { mPendingHubEndpointInfo = pendingEndpointInfo; mLifecycleCallback = endpointLifecycleCallback; @@ -485,12 +485,12 @@ public class HubEndpoint { } @Nullable - public IHubEndpointLifecycleCallback getLifecycleCallback() { + public HubEndpointLifecycleCallback getLifecycleCallback() { return mLifecycleCallback; } @Nullable - public IHubEndpointMessageCallback getMessageCallback() { + public HubEndpointMessageCallback getMessageCallback() { return mMessageCallback; } @@ -498,11 +498,11 @@ public class HubEndpoint { public static final class Builder { private final String mPackageName; - @Nullable private IHubEndpointLifecycleCallback mLifecycleCallback; + @Nullable private HubEndpointLifecycleCallback mLifecycleCallback; @NonNull private Executor mLifecycleCallbackExecutor; - @Nullable private IHubEndpointMessageCallback mMessageCallback; + @Nullable private HubEndpointMessageCallback mMessageCallback; @NonNull private Executor mMessageCallbackExecutor; private int mVersion; @@ -539,10 +539,13 @@ public class HubEndpoint { return this; } - /** Attach a callback interface for lifecycle events for this Endpoint */ + /** + * Attach a callback interface for lifecycle events for this Endpoint. Callback will be + * posted to the main thread. + */ @NonNull public Builder setLifecycleCallback( - @NonNull IHubEndpointLifecycleCallback lifecycleCallback) { + @NonNull HubEndpointLifecycleCallback lifecycleCallback) { mLifecycleCallback = lifecycleCallback; return this; } @@ -554,15 +557,18 @@ public class HubEndpoint { @NonNull public Builder setLifecycleCallback( @NonNull @CallbackExecutor Executor executor, - @NonNull IHubEndpointLifecycleCallback lifecycleCallback) { + @NonNull HubEndpointLifecycleCallback lifecycleCallback) { mLifecycleCallbackExecutor = executor; mLifecycleCallback = lifecycleCallback; return this; } - /** Attach a callback interface for message events for this Endpoint */ + /** + * Attach a callback interface for message events for this Endpoint. Callback will be posted + * to the main thread. + */ @NonNull - public Builder setMessageCallback(@NonNull IHubEndpointMessageCallback messageCallback) { + public Builder setMessageCallback(@NonNull HubEndpointMessageCallback messageCallback) { mMessageCallback = messageCallback; return this; } @@ -574,7 +580,7 @@ public class HubEndpoint { @NonNull public Builder setMessageCallback( @NonNull @CallbackExecutor Executor executor, - @NonNull IHubEndpointMessageCallback messageCallback) { + @NonNull HubEndpointMessageCallback messageCallback) { mMessageCallbackExecutor = executor; mMessageCallback = messageCallback; return this; diff --git a/core/java/android/hardware/contexthub/IHubEndpointDiscoveryCallback.java b/core/java/android/hardware/contexthub/HubEndpointDiscoveryCallback.java index a61a7ebd0de9..4672bbb74170 100644 --- a/core/java/android/hardware/contexthub/IHubEndpointDiscoveryCallback.java +++ b/core/java/android/hardware/contexthub/HubEndpointDiscoveryCallback.java @@ -30,7 +30,7 @@ import java.util.List; */ @SystemApi @FlaggedApi(Flags.FLAG_OFFLOAD_API) -public interface IHubEndpointDiscoveryCallback { +public interface HubEndpointDiscoveryCallback { /** * Called when a list of hub endpoints have started. * diff --git a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java b/core/java/android/hardware/contexthub/HubEndpointLifecycleCallback.java index 698ed0adfd80..6d75010711cc 100644 --- a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java +++ b/core/java/android/hardware/contexthub/HubEndpointLifecycleCallback.java @@ -29,7 +29,7 @@ import android.chre.flags.Flags; */ @SystemApi @FlaggedApi(Flags.FLAG_OFFLOAD_API) -public interface IHubEndpointLifecycleCallback { +public interface HubEndpointLifecycleCallback { /** * Called when an endpoint is requesting a session be opened with another endpoint. * diff --git a/core/java/android/hardware/contexthub/IHubEndpointMessageCallback.java b/core/java/android/hardware/contexthub/HubEndpointMessageCallback.java index fde7017b5e76..5db54efc8893 100644 --- a/core/java/android/hardware/contexthub/IHubEndpointMessageCallback.java +++ b/core/java/android/hardware/contexthub/HubEndpointMessageCallback.java @@ -26,18 +26,18 @@ import android.chre.flags.Flags; * <p>This interface can be attached to an endpoint through {@link * HubEndpoint.Builder#setMessageCallback} method. Methods in this interface will only be called * when the endpoint is currently registered and has an open session. The endpoint will receive - * session lifecycle callbacks through {@link IHubEndpointLifecycleCallback}. + * session lifecycle callbacks through {@link HubEndpointLifecycleCallback}. * * @hide */ @SystemApi @FlaggedApi(Flags.FLAG_OFFLOAD_API) -public interface IHubEndpointMessageCallback { +public interface HubEndpointMessageCallback { /** * Callback interface for receiving messages for a particular endpoint session. * * @param session The session this message is sent through. Previously specified in a {@link - * IHubEndpointLifecycleCallback#onSessionOpened(HubEndpointSession)} call. + * HubEndpointLifecycleCallback#onSessionOpened(HubEndpointSession)} call. * @param message The {@link HubMessage} object representing a message received by the endpoint * that registered this callback interface. This message is constructed by the */ diff --git a/core/java/android/hardware/contexthub/HubEndpointSession.java b/core/java/android/hardware/contexthub/HubEndpointSession.java index 77f937ebeabc..f7f5636264e4 100644 --- a/core/java/android/hardware/contexthub/HubEndpointSession.java +++ b/core/java/android/hardware/contexthub/HubEndpointSession.java @@ -137,7 +137,7 @@ public class HubEndpointSession implements AutoCloseable { * no service associated to this session. * * <p>For hub initiated sessions, the object was previously used in as an argument for open - * request in {@link IHubEndpointLifecycleCallback#onSessionOpenRequest}. + * request in {@link HubEndpointLifecycleCallback#onSessionOpenRequest}. * * <p>For app initiated sessions, the object was previously used in an open request in {@link * android.hardware.location.ContextHubManager#openSession} diff --git a/core/java/android/hardware/contexthub/HubEndpointSessionResult.java b/core/java/android/hardware/contexthub/HubEndpointSessionResult.java index 1f2bdb985008..b193d00467aa 100644 --- a/core/java/android/hardware/contexthub/HubEndpointSessionResult.java +++ b/core/java/android/hardware/contexthub/HubEndpointSessionResult.java @@ -23,7 +23,7 @@ import android.annotation.SystemApi; import android.chre.flags.Flags; /** - * Return type of {@link IHubEndpointLifecycleCallback#onSessionOpenRequest}. The value determines + * Return type of {@link HubEndpointLifecycleCallback#onSessionOpenRequest}. The value determines * whether a open session request from the remote is accepted or not. * * @hide diff --git a/core/java/android/hardware/contexthub/HubServiceInfo.java b/core/java/android/hardware/contexthub/HubServiceInfo.java index 2f33e8f8872b..651be3b17c33 100644 --- a/core/java/android/hardware/contexthub/HubServiceInfo.java +++ b/core/java/android/hardware/contexthub/HubServiceInfo.java @@ -112,6 +112,7 @@ public final class HubServiceInfo implements Parcelable { * <p>The value can be one of {@link HubServiceInfo#FORMAT_CUSTOM}, {@link * HubServiceInfo#FORMAT_AIDL} or {@link HubServiceInfo#FORMAT_PW_RPC_PROTOBUF}. */ + @ServiceFormat public int getFormat() { return mFormat; } @@ -178,7 +179,8 @@ public final class HubServiceInfo implements Parcelable { * <li>Pigweed RPC with Protobuf: com.example.proto.ExampleService * </ol> * - * @param serviceDescriptor The service descriptor. + * @param serviceDescriptor The service descriptor for the interface, provided by the + * vendor. * @param format One of {@link HubServiceInfo#FORMAT_CUSTOM}, {@link * HubServiceInfo#FORMAT_AIDL} or {@link HubServiceInfo#FORMAT_PW_RPC_PROTOBUF}. * @param majorVersion Breaking changes should be a major version bump. diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index ffa546067eff..9030810a1c1a 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -106,7 +106,7 @@ public final class DisplayManagerGlobal { @IntDef(prefix = {"EVENT_DISPLAY_"}, flag = true, value = { EVENT_DISPLAY_ADDED, - EVENT_DISPLAY_CHANGED, + EVENT_DISPLAY_BASIC_CHANGED, EVENT_DISPLAY_REMOVED, EVENT_DISPLAY_BRIGHTNESS_CHANGED, EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED, @@ -119,7 +119,8 @@ public final class DisplayManagerGlobal { public @interface DisplayEvent {} public static final int EVENT_DISPLAY_ADDED = 1; - public static final int EVENT_DISPLAY_CHANGED = 2; + public static final int EVENT_DISPLAY_BASIC_CHANGED = 2; + public static final int EVENT_DISPLAY_REMOVED = 3; public static final int EVENT_DISPLAY_BRIGHTNESS_CHANGED = 4; public static final int EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED = 5; @@ -130,7 +131,7 @@ public final class DisplayManagerGlobal { @LongDef(prefix = {"INTERNAL_EVENT_FLAG_"}, flag = true, value = { INTERNAL_EVENT_FLAG_DISPLAY_ADDED, - INTERNAL_EVENT_FLAG_DISPLAY_CHANGED, + INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED, INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED, INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED, @@ -143,7 +144,7 @@ public final class DisplayManagerGlobal { public @interface InternalEventFlag {} public static final long INTERNAL_EVENT_FLAG_DISPLAY_ADDED = 1L << 0; - public static final long INTERNAL_EVENT_FLAG_DISPLAY_CHANGED = 1L << 1; + public static final long INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED = 1L << 1; public static final long INTERNAL_EVENT_FLAG_DISPLAY_REMOVED = 1L << 2; public static final long INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED = 1L << 3; public static final long INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED = 1L << 4; @@ -485,7 +486,7 @@ public final class DisplayManagerGlobal { // There can be racing condition between DMS and WMS callbacks, so force triggering the // listener to make sure the client can get the onDisplayChanged callback even if // DisplayInfo is not changed (Display read from both DisplayInfo and WindowConfiguration). - handleDisplayEvent(displayId, EVENT_DISPLAY_CHANGED, true /* forceUpdate */); + handleDisplayEvent(displayId, EVENT_DISPLAY_BASIC_CHANGED, true /* forceUpdate */); } private static Looper getLooperForHandler(@Nullable Handler handler) { @@ -518,7 +519,8 @@ public final class DisplayManagerGlobal { } if (mDispatchNativeCallbacks) { mask |= INTERNAL_EVENT_FLAG_DISPLAY_ADDED - | INTERNAL_EVENT_FLAG_DISPLAY_CHANGED + | INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED + | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE | INTERNAL_EVENT_FLAG_DISPLAY_REMOVED; } if (!mTopologyListeners.isEmpty()) { @@ -571,7 +573,8 @@ public final class DisplayManagerGlobal { } info = getDisplayInfoLocked(displayId); - if (event == EVENT_DISPLAY_CHANGED && mDispatchNativeCallbacks) { + if ((event == EVENT_DISPLAY_BASIC_CHANGED + || event == EVENT_DISPLAY_REFRESH_RATE_CHANGED) && mDispatchNativeCallbacks) { // Choreographer only supports a single display, so only dispatch refresh rate // changes for the default display. if (displayId == Display.DEFAULT_DISPLAY) { @@ -1492,9 +1495,9 @@ public final class DisplayManagerGlobal { mListener.onDisplayAdded(displayId); } break; - case EVENT_DISPLAY_CHANGED: - if ((mInternalEventFlagsMask & INTERNAL_EVENT_FLAG_DISPLAY_CHANGED) - != 0) { + case EVENT_DISPLAY_BASIC_CHANGED: + if ((mInternalEventFlagsMask + & INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED) != 0) { if (info != null && (forceUpdate || !info.equals(mDisplayInfo))) { if (extraLogging()) { Slog.i(TAG, "Sending onDisplayChanged: Display Changed. Info: " @@ -1691,8 +1694,8 @@ public final class DisplayManagerGlobal { switch (event) { case EVENT_DISPLAY_ADDED: return "ADDED"; - case EVENT_DISPLAY_CHANGED: - return "CHANGED"; + case EVENT_DISPLAY_BASIC_CHANGED: + return "BASIC_CHANGED"; case EVENT_DISPLAY_REMOVED: return "REMOVED"; case EVENT_DISPLAY_BRIGHTNESS_CHANGED: @@ -1763,7 +1766,11 @@ public final class DisplayManagerGlobal { } if ((eventFlags & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0) { - baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_CHANGED; + // For backward compatibility, a client subscribing to + // DisplayManager.EVENT_FLAG_DISPLAY_CHANGED will be enrolled to both Basic and + // RR changes + baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED + | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE; } if ((eventFlags diff --git a/core/java/android/hardware/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java index 211aefffa34c..ba5dfc094afb 100644 --- a/core/java/android/hardware/display/DisplayTopology.java +++ b/core/java/android/hardware/display/DisplayTopology.java @@ -129,14 +129,38 @@ public final class DisplayTopology implements Parcelable { } /** + * Update the size of a display and normalize the topology. + * @param displayId The logical display ID + * @param width The new width + * @param height The new height + * @return True if the topology has changed. + */ + public boolean updateDisplay(int displayId, float width, float height) { + TreeNode display = findDisplay(displayId, mRoot); + if (display == null) { + return false; + } + if (floatEquals(display.mWidth, width) && floatEquals(display.mHeight, height)) { + return false; + } + display.mWidth = width; + display.mHeight = height; + normalize(); + Slog.i(TAG, "Display with ID " + displayId + " updated, new width: " + width + + ", new height: " + height); + return true; + } + + /** * Remove a display from the topology. * The default topology is created from the remaining displays, as if they were reconnected * one by one. * @param displayId The logical display ID + * @return True if the display was present in the topology and removed. */ - public void removeDisplay(int displayId) { + public boolean removeDisplay(int displayId) { if (findDisplay(displayId, mRoot) == null) { - return; + return false; } Queue<TreeNode> queue = new ArrayDeque<>(); queue.add(mRoot); @@ -159,6 +183,7 @@ public final class DisplayTopology implements Parcelable { } else { Slog.i(TAG, "Display with ID " + displayId + " removed"); } + return true; } /** @@ -685,12 +710,12 @@ public final class DisplayTopology implements Parcelable { /** * The width of the display in density-independent pixels (dp). */ - private final float mWidth; + private float mWidth; /** * The height of the display in density-independent pixels (dp). */ - private final float mHeight; + private float mHeight; /** * The position of this display relative to its parent. diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig index 313bad50e88e..c4d11cd8aff7 100644 --- a/core/java/android/hardware/input/input_framework.aconfig +++ b/core/java/android/hardware/input/input_framework.aconfig @@ -204,3 +204,10 @@ flag { description: "Allows the user to disable input scrolling acceleration for mouse." bug: "383555305" } + +flag { + name: "remove_fallback_modifiers" + namespace: "input" + description: "Removes modifiers from the original key event that activated the fallback, ensuring that only the intended fallback event is sent." + bug: "382545048" +} diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index 1e0cc94612dd..0cd320981c93 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -36,11 +36,11 @@ import android.content.pm.PackageManager; import android.hardware.contexthub.ErrorCode; import android.hardware.contexthub.HubDiscoveryInfo; import android.hardware.contexthub.HubEndpoint; +import android.hardware.contexthub.HubEndpointDiscoveryCallback; import android.hardware.contexthub.HubEndpointInfo; +import android.hardware.contexthub.HubEndpointLifecycleCallback; import android.hardware.contexthub.HubServiceInfo; import android.hardware.contexthub.IContextHubEndpointDiscoveryCallback; -import android.hardware.contexthub.IHubEndpointDiscoveryCallback; -import android.hardware.contexthub.IHubEndpointLifecycleCallback; import android.os.Handler; import android.os.HandlerExecutor; import android.os.Looper; @@ -207,7 +207,7 @@ public final class ContextHubManager { private Handler mCallbackHandler; /** A map of endpoint discovery callbacks currently registered */ - private Map<IHubEndpointDiscoveryCallback, IContextHubEndpointDiscoveryCallback> + private Map<HubEndpointDiscoveryCallback, IContextHubEndpointDiscoveryCallback> mDiscoveryCallbacks = new ConcurrentHashMap<>(); /** @@ -718,7 +718,19 @@ public final class ContextHubManager { /** * Find a list of endpoints that provides a specific service. * - * @param serviceDescriptor Statically generated ID for an endpoint. + * <p>Service descriptor should uniquely identify the interface (scoped to type). Convention of + * the descriptor depend on interface type. + * + * <p>Examples: + * + * <ol> + * <li>AOSP-defined AIDL: android.hardware.something.IFoo/default + * <li>Vendor-defined AIDL: com.example.something.IBar/default + * <li>Pigweed RPC with Protobuf: com.example.proto.ExampleService + * </ol> + * + * @param serviceDescriptor The service descriptor for a service provided by the hub. The value + * cannot be null or empty. * @return A list of {@link HubDiscoveryInfo} objects that represents the result of discovery. * @throws IllegalArgumentException if the serviceDescriptor is empty/null. */ @@ -750,14 +762,15 @@ public final class ContextHubManager { /** * Creates an interface to invoke endpoint discovery callbacks to send down to the service. * - * @param callback the callback to invoke at the client process * @param executor the executor to invoke callbacks for this client + * @param callback the callback to invoke at the client process + * @param serviceDescriptor an optional descriptor to match discovery list with * @return the callback interface */ @FlaggedApi(Flags.FLAG_OFFLOAD_API) private IContextHubEndpointDiscoveryCallback createDiscoveryCallback( - IHubEndpointDiscoveryCallback callback, Executor executor, + HubEndpointDiscoveryCallback callback, @Nullable String serviceDescriptor) { return new IContextHubEndpointDiscoveryCallback.Stub() { @Override @@ -829,36 +842,36 @@ public final class ContextHubManager { } /** - * Equivalent to {@link #registerEndpointDiscoveryCallback(long, IHubEndpointDiscoveryCallback, - * Executor)} with the default executor in the main thread. + * Equivalent to {@link #registerEndpointDiscoveryCallback(Executor, + * HubEndpointDiscoveryCallback, long)} with the default executor in the main thread. */ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @FlaggedApi(Flags.FLAG_OFFLOAD_API) public void registerEndpointDiscoveryCallback( - long endpointId, @NonNull IHubEndpointDiscoveryCallback callback) { + @NonNull HubEndpointDiscoveryCallback callback, long endpointId) { registerEndpointDiscoveryCallback( - endpointId, callback, new HandlerExecutor(Handler.getMain())); + new HandlerExecutor(Handler.getMain()), callback, endpointId); } /** * Registers a callback to be notified when the hub endpoint with the corresponding endpoint ID * has started or stopped. * - * @param endpointId The identifier of the hub endpoint. - * @param callback The callback to be invoked. * @param executor The executor to invoke the callback on. + * @param callback The callback to be invoked. + * @param endpointId The identifier of the hub endpoint. * @throws UnsupportedOperationException If the operation is not supported. */ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @FlaggedApi(Flags.FLAG_OFFLOAD_API) public void registerEndpointDiscoveryCallback( - long endpointId, - @NonNull IHubEndpointDiscoveryCallback callback, - @NonNull Executor executor) { - Objects.requireNonNull(callback, "callback cannot be null"); + @NonNull Executor executor, + @NonNull HubEndpointDiscoveryCallback callback, + long endpointId) { Objects.requireNonNull(executor, "executor cannot be null"); + Objects.requireNonNull(callback, "callback cannot be null"); IContextHubEndpointDiscoveryCallback iCallback = - createDiscoveryCallback(callback, executor, null); + createDiscoveryCallback(executor, callback, null); try { mService.registerEndpointDiscoveryCallbackId(endpointId, iCallback); } catch (RemoteException e) { @@ -869,42 +882,42 @@ public final class ContextHubManager { } /** - * Equivalent to {@link #registerEndpointDiscoveryCallback(String, - * IHubEndpointDiscoveryCallback, Executor)} with the default executor in the main thread. + * Equivalent to {@link #registerEndpointDiscoveryCallback(Executor, + * HubEndpointDiscoveryCallback, String)} with the default executor in the main thread. */ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @FlaggedApi(Flags.FLAG_OFFLOAD_API) public void registerEndpointDiscoveryCallback( - @NonNull String serviceDescriptor, @NonNull IHubEndpointDiscoveryCallback callback) { + @NonNull HubEndpointDiscoveryCallback callback, @NonNull String serviceDescriptor) { registerEndpointDiscoveryCallback( - serviceDescriptor, callback, new HandlerExecutor(Handler.getMain())); + new HandlerExecutor(Handler.getMain()), callback, serviceDescriptor); } /** * Registers a callback to be notified when the hub endpoint with the corresponding service * descriptor has started or stopped. * + * @param executor The executor to invoke the callback on. * @param serviceDescriptor The service descriptor of the hub endpoint. * @param callback The callback to be invoked. - * @param executor The executor to invoke the callback on. * @throws IllegalArgumentException if the serviceDescriptor is empty. * @throws UnsupportedOperationException If the operation is not supported. */ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @FlaggedApi(Flags.FLAG_OFFLOAD_API) public void registerEndpointDiscoveryCallback( - @NonNull String serviceDescriptor, - @NonNull IHubEndpointDiscoveryCallback callback, - @NonNull Executor executor) { - Objects.requireNonNull(serviceDescriptor, "serviceDescriptor cannot be null"); - Objects.requireNonNull(callback, "callback cannot be null"); + @NonNull Executor executor, + @NonNull HubEndpointDiscoveryCallback callback, + @NonNull String serviceDescriptor) { Objects.requireNonNull(executor, "executor cannot be null"); + Objects.requireNonNull(callback, "callback cannot be null"); + Objects.requireNonNull(serviceDescriptor, "serviceDescriptor cannot be null"); if (serviceDescriptor.isBlank()) { throw new IllegalArgumentException("Invalid service descriptor: " + serviceDescriptor); } IContextHubEndpointDiscoveryCallback iCallback = - createDiscoveryCallback(callback, executor, serviceDescriptor); + createDiscoveryCallback(executor, callback, serviceDescriptor); try { mService.registerEndpointDiscoveryCallbackDescriptor(serviceDescriptor, iCallback); } catch (RemoteException e) { @@ -924,7 +937,7 @@ public final class ContextHubManager { @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @FlaggedApi(Flags.FLAG_OFFLOAD_API) public void unregisterEndpointDiscoveryCallback( - @NonNull IHubEndpointDiscoveryCallback callback) { + @NonNull HubEndpointDiscoveryCallback callback) { Objects.requireNonNull(callback, "callback cannot be null"); IContextHubEndpointDiscoveryCallback iCallback = mDiscoveryCallbacks.remove(callback); if (iCallback == null) { @@ -1291,7 +1304,7 @@ public final class ContextHubManager { * service. * * <p>Context Hub Service will create the endpoint session and notify the registered endpoint. - * The registered endpoint will receive callbacks on its {@link IHubEndpointLifecycleCallback} + * The registered endpoint will receive callbacks on its {@link HubEndpointLifecycleCallback} * object regarding the lifecycle events of the session. * * @param hubEndpoint {@link HubEndpoint} object previously registered via {@link @@ -1311,7 +1324,7 @@ public final class ContextHubManager { * described by a {@link HubServiceInfo} object. * * <p>Context Hub Service will create the endpoint session and notify the registered endpoint. - * The registered endpoint will receive callbacks on its {@link IHubEndpointLifecycleCallback} + * The registered endpoint will receive callbacks on its {@link HubEndpointLifecycleCallback} * object regarding the lifecycle events of the session. * * @param hubEndpoint {@link HubEndpoint} object previously registered via {@link diff --git a/core/java/android/net/thread/flags.aconfig b/core/java/android/net/thread/flags.aconfig index afb982ba64ca..100d50d8e70c 100644 --- a/core/java/android/net/thread/flags.aconfig +++ b/core/java/android/net/thread/flags.aconfig @@ -17,5 +17,5 @@ flag { is_exported: true namespace: "thread_network" description: "Controls whether the Android Thread feature is enabled" - bug: "301473012" + bug: "384596973" } diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java index 50121278f0e6..ecd90e46e432 100644 --- a/core/java/android/os/BaseBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -45,7 +45,8 @@ import java.util.function.BiFunction; * {@link PersistableBundle} subclass. */ @android.ravenwood.annotation.RavenwoodKeepWholeClass -public class BaseBundle { +@SuppressWarnings("HiddenSuperclass") +public class BaseBundle implements Parcel.ClassLoaderProvider { /** @hide */ protected static final String TAG = "Bundle"; static final boolean DEBUG = false; @@ -299,8 +300,9 @@ public class BaseBundle { /** * Return the ClassLoader currently associated with this Bundle. + * @hide */ - ClassLoader getClassLoader() { + public ClassLoader getClassLoader() { return mClassLoader; } @@ -405,6 +407,9 @@ public class BaseBundle { if ((mFlags & Bundle.FLAG_VERIFY_TOKENS_PRESENT) != 0) { Intent.maybeMarkAsMissingCreatorToken(object); } + } else if (object instanceof Bundle) { + Bundle bundle = (Bundle) object; + bundle.setClassLoaderSameAsContainerBundleWhenRetrievedFirstTime(this); } return (clazz != null) ? clazz.cast(object) : (T) object; } @@ -475,10 +480,10 @@ public class BaseBundle { map.erase(); map.ensureCapacity(count); } - int numLazyValues = 0; + int[] numLazyValues = new int[]{0}; try { - numLazyValues = parcelledData.readArrayMap(map, count, !parcelledByNative, - /* lazy */ ownsParcel, mClassLoader); + parcelledData.readArrayMap(map, count, !parcelledByNative, + /* lazy */ ownsParcel, this, numLazyValues); } catch (BadParcelableException e) { if (sShouldDefuse) { Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e); @@ -489,14 +494,14 @@ public class BaseBundle { } finally { mWeakParcelledData = null; if (ownsParcel) { - if (numLazyValues == 0) { + if (numLazyValues[0] == 0) { recycleParcel(parcelledData); } else { mWeakParcelledData = new WeakReference<>(parcelledData); } } - mLazyValues = numLazyValues; + mLazyValues = numLazyValues[0]; mParcelledByNative = false; mMap = map; // Set field last as it is volatile diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java index 819d58d9f059..55bfd451d97a 100644 --- a/core/java/android/os/Bundle.java +++ b/core/java/android/os/Bundle.java @@ -141,6 +141,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { STRIPPED.putInt("STRIPPED", 1); } + private boolean isFirstRetrievedFromABundle = false; + /** * Constructs a new, empty Bundle. */ @@ -1020,7 +1022,9 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { return null; } try { - return (Bundle) o; + Bundle bundle = (Bundle) o; + bundle.setClassLoaderSameAsContainerBundleWhenRetrievedFirstTime(this); + return bundle; } catch (ClassCastException e) { typeWarning(key, o, "Bundle", e); return null; @@ -1028,6 +1032,21 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { } /** + * Set the ClassLoader of a bundle to its container bundle. This is necessary so that when a + * bundle's ClassLoader is changed, it can be propagated to its children. Do this only when it + * is retrieved from the container bundle first time though. Once it is accessed outside of its + * container, its ClassLoader should no longer be changed by its container anymore. + * + * @param containerBundle the bundle this bundle is retrieved from. + */ + void setClassLoaderSameAsContainerBundleWhenRetrievedFirstTime(BaseBundle containerBundle) { + if (!isFirstRetrievedFromABundle) { + setClassLoader(containerBundle.getClassLoader()); + isFirstRetrievedFromABundle = true; + } + } + + /** * Returns the value associated with the given key, or {@code null} if * no mapping of the desired type exists for the given key or a {@code null} * value is explicitly associated with the key. diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index cf473ec9c3ea..5ba6553a58c9 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -4650,7 +4650,7 @@ public final class Parcel { * @hide */ @Nullable - public Object readLazyValue(@Nullable ClassLoader loader) { + private Object readLazyValue(@Nullable ClassLoaderProvider loaderProvider) { int start = dataPosition(); int type = readInt(); if (isLengthPrefixed(type)) { @@ -4661,12 +4661,17 @@ public final class Parcel { int end = MathUtils.addOrThrow(dataPosition(), objectLength); int valueLength = end - start; setDataPosition(end); - return new LazyValue(this, start, valueLength, type, loader); + return new LazyValue(this, start, valueLength, type, loaderProvider); } else { - return readValue(type, loader, /* clazz */ null); + return readValue(type, getClassLoader(loaderProvider), /* clazz */ null); } } + @Nullable + private static ClassLoader getClassLoader(@Nullable ClassLoaderProvider loaderProvider) { + return loaderProvider == null ? null : loaderProvider.getClassLoader(); + } + private static final class LazyValue implements BiFunction<Class<?>, Class<?>[], Object> { /** @@ -4680,7 +4685,12 @@ public final class Parcel { private final int mPosition; private final int mLength; private final int mType; - @Nullable private final ClassLoader mLoader; + // this member is set when a bundle that includes a LazyValue is unparceled. But it is used + // when apply method is called. Between these 2 events, the bundle's ClassLoader could have + // changed. Let the bundle be a ClassLoaderProvider allows the bundle provides its current + // ClassLoader at the time apply method is called. + @NonNull + private final ClassLoaderProvider mLoaderProvider; @Nullable private Object mObject; /** @@ -4691,12 +4701,13 @@ public final class Parcel { */ @Nullable private volatile Parcel mSource; - LazyValue(Parcel source, int position, int length, int type, @Nullable ClassLoader loader) { + LazyValue(Parcel source, int position, int length, int type, + @NonNull ClassLoaderProvider loaderProvider) { mSource = requireNonNull(source); mPosition = position; mLength = length; mType = type; - mLoader = loader; + mLoaderProvider = loaderProvider; } @Override @@ -4709,7 +4720,8 @@ public final class Parcel { int restore = source.dataPosition(); try { source.setDataPosition(mPosition); - mObject = source.readValue(mLoader, clazz, itemTypes); + mObject = source.readValue(mLoaderProvider.getClassLoader(), clazz, + itemTypes); } finally { source.setDataPosition(restore); } @@ -4782,7 +4794,8 @@ public final class Parcel { return Objects.equals(mObject, value.mObject); } // Better safely fail here since this could mean we get different objects. - if (!Objects.equals(mLoader, value.mLoader)) { + if (!Objects.equals(mLoaderProvider.getClassLoader(), + value.mLoaderProvider.getClassLoader())) { return false; } // Otherwise compare metadata prior to comparing payload. @@ -4796,10 +4809,24 @@ public final class Parcel { @Override public int hashCode() { // Accessing mSource first to provide memory barrier for mObject - return Objects.hash(mSource == null, mObject, mLoader, mType, mLength); + return Objects.hash(mSource == null, mObject, mLoaderProvider.getClassLoader(), mType, + mLength); } } + /** + * Provides a ClassLoader. + * @hide + */ + public interface ClassLoaderProvider { + /** + * Returns a ClassLoader. + * + * @return ClassLoader + */ + ClassLoader getClassLoader(); + } + /** Same as {@link #readValue(ClassLoader, Class, Class[])} without any item types. */ private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz) { // Avoids allocating Class[0] array @@ -5537,8 +5564,8 @@ public final class Parcel { } private void readArrayMapInternal(@NonNull ArrayMap<? super String, Object> outVal, - int size, @Nullable ClassLoader loader) { - readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loader); + int size, @Nullable ClassLoaderProvider loaderProvider) { + readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loaderProvider, null); } /** @@ -5548,17 +5575,17 @@ public final class Parcel { * @param lazy Whether to populate the map with lazy {@link Function} objects for * length-prefixed values. See {@link Parcel#readLazyValue(ClassLoader)} for more * details. - * @return a count of the lazy values in the map + * @param lazyValueCount number of lazy values added here * @hide */ - int readArrayMap(ArrayMap<? super String, Object> map, int size, boolean sorted, - boolean lazy, @Nullable ClassLoader loader) { - int lazyValues = 0; + void readArrayMap(ArrayMap<? super String, Object> map, int size, boolean sorted, + boolean lazy, @Nullable ClassLoaderProvider loaderProvider, int[] lazyValueCount) { while (size > 0) { String key = readString(); - Object value = (lazy) ? readLazyValue(loader) : readValue(loader); + Object value = (lazy) ? readLazyValue(loaderProvider) : readValue( + getClassLoader(loaderProvider)); if (value instanceof LazyValue) { - lazyValues++; + lazyValueCount[0]++; } if (sorted) { map.append(key, value); @@ -5570,7 +5597,6 @@ public final class Parcel { if (sorted) { map.validate(); } - return lazyValues; } /** @@ -5578,12 +5604,12 @@ public final class Parcel { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void readArrayMap(@NonNull ArrayMap<? super String, Object> outVal, - @Nullable ClassLoader loader) { + @Nullable ClassLoaderProvider loaderProvider) { final int N = readInt(); if (N < 0) { return; } - readArrayMapInternal(outVal, N, loader); + readArrayMapInternal(outVal, N, loaderProvider); } /** diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 907d96834857..0c5d9e97a77d 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -1221,6 +1221,17 @@ public class Process { */ public static final native int[] getExclusiveCores(); + + /** + * Get the CPU affinity masks from sched_getaffinity. + * + * @param tid The identifier of the thread/process to get the sched affinity. + * @return an array of CPU affinity masks, of which the size will be dynamic and just enough to + * include all bit masks for all currently online and possible CPUs of the device. + * @hide + */ + public static final native long[] getSchedAffinity(int tid); + /** * Set the priority of the calling thread, based on Linux priorities. See * {@link #setThreadPriority(int, int)} for more information. diff --git a/core/java/android/os/TestLooperManager.java b/core/java/android/os/TestLooperManager.java index e2169925fdd3..289b98c9b1d4 100644 --- a/core/java/android/os/TestLooperManager.java +++ b/core/java/android/os/TestLooperManager.java @@ -41,6 +41,7 @@ public class TestLooperManager { private boolean mReleased; private boolean mLooperBlocked; + private final boolean mLooperIsMyLooper; /** * @hide @@ -54,8 +55,11 @@ public class TestLooperManager { } mLooper = looper; mQueue = mLooper.getQueue(); - // Post a message that will keep the looper blocked as long as we are dispatching. - new Handler(looper).post(new LooperHolder()); + mLooperIsMyLooper = Looper.myLooper() == looper; + if (!mLooperIsMyLooper) { + // Post a message that will keep the looper blocked as long as we are dispatching. + new Handler(looper).post(new LooperHolder()); + } } /** @@ -82,7 +86,7 @@ public class TestLooperManager { public Message next() { // Wait for the looper block to come up, to make sure we don't accidentally get // the message for the block. - while (!mLooperBlocked) { + while (!mLooperIsMyLooper && !mLooperBlocked) { synchronized (this) { try { wait(); @@ -114,9 +118,6 @@ public class TestLooperManager { * should be executed by this queue. * If the queue is empty or no messages are deliverable, returns null. * This method never blocks. - * - * <p>Callers should always call {@link #recycle(Message)} on the message when all interactions - * with it have completed. */ @FlaggedApi(Flags.FLAG_MESSAGE_QUEUE_TESTABILITY) @SuppressWarnings("AutoBoxing") // box the primitive long, or return null to indicate no value @@ -165,6 +166,9 @@ public class TestLooperManager { // This is being called from the thread it should be executed on, we can just dispatch. message.target.dispatchMessage(message); } else { + if (mLooperIsMyLooper) { + throw new RuntimeException("Cannot call execute from non Looper thread"); + } MessageExecution execution = new MessageExecution(); execution.m = message; synchronized (execution) { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index c3a49305af87..6898fcef23ab 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1296,6 +1296,22 @@ public final class Settings { public static final String ACTION_LOCKSCREEN_SETTINGS = "android.settings.LOCK_SCREEN_SETTINGS"; /** + * Activity Action: Show settings of notifications on lockscreen. + * <p> + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_LOCKSCREEN_NOTIFICATIONS_SETTINGS = + "android.settings.LOCK_SCREEN_NOTIFICATIONS_SETTINGS"; + + /** * Activity Action: Show settings to allow pairing bluetooth devices. * <p> * In some cases, a matching Activity may not exist, so ensure you diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java index 90136ae00f6a..ffe8086ca4a1 100644 --- a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java +++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java @@ -93,6 +93,10 @@ import android.util.Log; * must do its own state management (keeping in mind that the service's process might be killed * by the Android System when unbound; for example, if the device is running low in memory). * + * <p> The service also provides pending intents to override the system's Quick Access activities + * via the {@link #getTargetActivityPendingIntent} and the + * {@link #getGestureTargetActivityPendingIntent} method. + * * <p> * <a name="ErrorHandling"></a> * <h3>Error handling</h3> @@ -384,6 +388,10 @@ public abstract class QuickAccessWalletService extends Service { * * <p>The pending intent will be sent when the user performs a gesture to open Wallet. * The pending intent should launch an activity. + * + * <p> If the gesture is performed and this method returns null, the system will launch the + * activity specified by the {@link #getTargetActivityPendingIntent} method. If that method + * also returns null, the system will launch the system-provided card switcher activity. */ @Nullable @FlaggedApi(Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) diff --git a/core/java/android/service/settings/preferences/GetValueRequest.java b/core/java/android/service/settings/preferences/GetValueRequest.java index 4f82800d1855..db5c57c49595 100644 --- a/core/java/android/service/settings/preferences/GetValueRequest.java +++ b/core/java/android/service/settings/preferences/GetValueRequest.java @@ -108,6 +108,7 @@ public final class GetValueRequest implements Parcelable { /** * Builder to construct {@link GetValueRequest}. */ + @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) public static final class Builder { private final String mScreenKey; private final String mPreferenceKey; diff --git a/core/java/android/service/settings/preferences/GetValueResult.java b/core/java/android/service/settings/preferences/GetValueResult.java index 369dea77cc85..791131588034 100644 --- a/core/java/android/service/settings/preferences/GetValueResult.java +++ b/core/java/android/service/settings/preferences/GetValueResult.java @@ -170,6 +170,7 @@ public final class GetValueResult implements Parcelable { /** * Builder to construct {@link GetValueResult}. */ + @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) public static final class Builder { @ResultCode private final int mResultCode; diff --git a/core/java/android/service/settings/preferences/MetadataRequest.java b/core/java/android/service/settings/preferences/MetadataRequest.java index ffecc6bec5b2..e0417152eedc 100644 --- a/core/java/android/service/settings/preferences/MetadataRequest.java +++ b/core/java/android/service/settings/preferences/MetadataRequest.java @@ -65,6 +65,7 @@ public final class MetadataRequest implements Parcelable { /** * Builder to construct {@link MetadataRequest}. */ + @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) public static final class Builder { /** Constructs an immutable {@link MetadataRequest} object. */ @NonNull diff --git a/core/java/android/service/settings/preferences/MetadataResult.java b/core/java/android/service/settings/preferences/MetadataResult.java index 6a65dcc9c757..e62fa8f38c93 100644 --- a/core/java/android/service/settings/preferences/MetadataResult.java +++ b/core/java/android/service/settings/preferences/MetadataResult.java @@ -131,6 +131,7 @@ public final class MetadataResult implements Parcelable { /** * Builder to construct {@link MetadataResult}. */ + @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) public static final class Builder { @ResultCode private final int mResultCode; diff --git a/core/java/android/service/settings/preferences/SetValueRequest.java b/core/java/android/service/settings/preferences/SetValueRequest.java index f7600aecdfaf..77581d9deffe 100644 --- a/core/java/android/service/settings/preferences/SetValueRequest.java +++ b/core/java/android/service/settings/preferences/SetValueRequest.java @@ -123,6 +123,7 @@ public final class SetValueRequest implements Parcelable { /** * Builder to construct {@link SetValueRequest}. */ + @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) public static final class Builder { private final String mScreenKey; private final String mPreferenceKey; diff --git a/core/java/android/service/settings/preferences/SetValueResult.java b/core/java/android/service/settings/preferences/SetValueResult.java index cb1776abd3bc..513f7a7d5bcc 100644 --- a/core/java/android/service/settings/preferences/SetValueResult.java +++ b/core/java/android/service/settings/preferences/SetValueResult.java @@ -156,6 +156,7 @@ public final class SetValueResult implements Parcelable { /** * Builder to construct {@link SetValueResult}. */ + @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) public static final class Builder { @ResultCode private final int mResultCode; diff --git a/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java b/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java index ea7d4a675713..30631f2fd71d 100644 --- a/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java +++ b/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java @@ -102,6 +102,7 @@ public final class SettingsPreferenceMetadata implements Parcelable { /** * Returns the breadcrumbs (navigation context) of Preference. * <p>May be empty. + * @hide restrict to platform; may be opened wider in the future */ @NonNull public List<String> getBreadcrumbs() { @@ -189,33 +190,32 @@ public final class SettingsPreferenceMetadata implements Parcelable { @IntDef(value = { NO_SENSITIVITY, EXPECT_POST_CONFIRMATION, - EXPECT_PRE_CONFIRMATION, + DEEPLINK_ONLY, NO_DIRECT_ACCESS, }) @Retention(RetentionPolicy.SOURCE) public @interface WriteSensitivity {} /** - * Indicates preference is not sensitive. + * Indicates preference is not write-sensitive. * <p>Its value is writable without explicit consent, assuming all necessary permissions are * granted. */ public static final int NO_SENSITIVITY = 0; /** - * Indicates preference is mildly sensitive. + * Indicates preference is mildly write-sensitive. * <p>In addition to necessary permissions, after writing its value the user should be * given the option to revert back. */ public static final int EXPECT_POST_CONFIRMATION = 1; /** - * Indicates preference is sensitive. - * <p>In addition to necessary permissions, the user should be prompted for confirmation prior - * to making a change. Otherwise it is suggested to provide a deeplink to the Preference's page - * instead, accessible via {@link #getLaunchIntent}. + * Indicates preference is write-sensitive. + * <p>This preference cannot be changed through this API; instead a deeplink to the Preference's + * page should be used instead, accessible via {@link #getLaunchIntent}. */ - public static final int EXPECT_PRE_CONFIRMATION = 2; + public static final int DEEPLINK_ONLY = 2; /** - * Indicates preference is highly sensitivity and carries significant user-risk. + * Indicates preference is highly write-sensitivity and carries significant user-risk. * <p>This Preference cannot be changed through this API and no direct deeplink is available. * Other Metadata is still available. */ @@ -303,6 +303,7 @@ public final class SettingsPreferenceMetadata implements Parcelable { /** * Builder to construct {@link SettingsPreferenceMetadata}. */ + @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) public static final class Builder { private final String mScreenKey; private final String mKey; @@ -355,6 +356,7 @@ public final class SettingsPreferenceMetadata implements Parcelable { /** * Sets the preference breadcrumbs (navigation context). + * @hide */ @NonNull public Builder setBreadcrumbs(@NonNull List<String> breadcrumbs) { diff --git a/core/java/android/service/settings/preferences/SettingsPreferenceValue.java b/core/java/android/service/settings/preferences/SettingsPreferenceValue.java index 08826ca9776b..eea93b321e47 100644 --- a/core/java/android/service/settings/preferences/SettingsPreferenceValue.java +++ b/core/java/android/service/settings/preferences/SettingsPreferenceValue.java @@ -170,6 +170,7 @@ public final class SettingsPreferenceValue implements Parcelable { /** * Builder to construct {@link SettingsPreferenceValue}. */ + @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) public static final class Builder { @Type private final int mType; diff --git a/core/java/android/tracing/flags.aconfig b/core/java/android/tracing/flags.aconfig index fb1bd1703ce6..6116d599baa0 100644 --- a/core/java/android/tracing/flags.aconfig +++ b/core/java/android/tracing/flags.aconfig @@ -70,3 +70,11 @@ flag { is_fixed_read_only: true bug: "352538294" } + +flag { + name: "system_server_large_perfetto_shmem_buffer" + namespace: "windowing_tools" + description: "Large perfetto shmem buffer" + is_fixed_read_only: true + bug: "382369925" +} diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 089b5c256b6e..6c50b5f945a5 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -16,6 +16,7 @@ package android.view; +import static android.view.flags.Flags.bufferStuffingRecovery; import static android.view.flags.Flags.FLAG_EXPECTED_PRESENTATION_TIME_API; import static android.view.DisplayEventReceiver.VSYNC_SOURCE_APP; import static android.view.DisplayEventReceiver.VSYNC_SOURCE_SURFACE_FLINGER; @@ -965,22 +966,24 @@ public final class Choreographer { // Evaluate if buffer stuffing recovery needs to start or end, and // what actions need to be taken for recovery. - switch (updateBufferStuffingState(frameTimeNanos, vsyncEventData)) { - case NONE: - // Without buffer stuffing recovery, offsetFrameTimeNanos is - // synonymous with frameTimeNanos. - break; - case OFFSET: - // Add animation offset. Used to update frame timeline with - // offset before jitter is calculated. - offsetFrameTimeNanos = frameTimeNanos - frameIntervalNanos; - break; - case DELAY_FRAME: - // Intentional frame delay to help reduce queued buffer count. - scheduleVsyncLocked(); - return; - default: - break; + if (bufferStuffingRecovery()) { + switch (updateBufferStuffingState(frameTimeNanos, vsyncEventData)) { + case NONE: + // Without buffer stuffing recovery, offsetFrameTimeNanos is + // synonymous with frameTimeNanos. + break; + case OFFSET: + // Add animation offset. Used to update frame timeline with + // offset before jitter is calculated. + offsetFrameTimeNanos = frameTimeNanos - frameIntervalNanos; + break; + case DELAY_FRAME: + // Intentional frame delay to help reduce queued buffer count. + scheduleVsyncLocked(); + return; + default: + break; + } } try { diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 0c8a0d60a96a..ca0959af3ff8 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -1597,7 +1597,9 @@ public final class Display { // Although we only care about the HDR/SDR ratio changing, that can also come in the // form of the larger DISPLAY_CHANGED event mGlobal.registerDisplayListener(toRegister, executor, - DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED + DisplayManagerGlobal + .INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE | DisplayManagerGlobal .INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED, ActivityThread.currentPackageName()); diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index ba098eb53246..e75b1b0bd17a 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -447,7 +447,18 @@ public final class DisplayInfo implements Parcelable { } public boolean equals(DisplayInfo other) { - return other != null + return equals(other, /* compareRefreshRate */ true); + } + + /** + * Compares if the two DisplayInfo objects are equal or not + * @param other The other DisplayInfo against which the comparison is to be done + * @param compareRefreshRate Indicates if the refresh rate is also to be considered in + * comparison + * @return + */ + public boolean equals(DisplayInfo other, boolean compareRefreshRate) { + boolean isEqualWithoutRefreshRate = other != null && layerStack == other.layerStack && flags == other.flags && type == other.type @@ -466,7 +477,6 @@ public final class DisplayInfo implements Parcelable { && logicalHeight == other.logicalHeight && Objects.equals(displayCutout, other.displayCutout) && rotation == other.rotation - && modeId == other.modeId && hasArrSupport == other.hasArrSupport && Objects.equals(frameRateCategoryRate, other.frameRateCategoryRate) && Arrays.equals(supportedRefreshRates, other.supportedRefreshRates) @@ -490,7 +500,6 @@ public final class DisplayInfo implements Parcelable { && ownerUid == other.ownerUid && Objects.equals(ownerPackageName, other.ownerPackageName) && removeMode == other.removeMode - && getRefreshRate() == other.getRefreshRate() && brightnessMinimum == other.brightnessMinimum && brightnessMaximum == other.brightnessMaximum && brightnessDefault == other.brightnessDefault @@ -504,6 +513,13 @@ public final class DisplayInfo implements Parcelable { && Objects.equals( thermalBrightnessThrottlingDataId, other.thermalBrightnessThrottlingDataId) && canHostTasks == other.canHostTasks; + + if (compareRefreshRate) { + return isEqualWithoutRefreshRate + && (getRefreshRate() == other.getRefreshRate()) + && (modeId == other.modeId); + } + return isEqualWithoutRefreshRate; } @Override diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java index a8d4e2d2c70a..48dfdd4a95f4 100644 --- a/core/java/android/view/KeyCharacterMap.java +++ b/core/java/android/view/KeyCharacterMap.java @@ -16,6 +16,9 @@ package android.view; + +import static com.android.hardware.input.Flags.removeFallbackModifiers; + import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; @@ -458,7 +461,15 @@ public class KeyCharacterMap implements Parcelable { FallbackAction action = FallbackAction.obtain(); metaState = KeyEvent.normalizeMetaState(metaState); if (nativeGetFallbackAction(mPtr, keyCode, metaState, action)) { - action.metaState = KeyEvent.normalizeMetaState(action.metaState); + if (removeFallbackModifiers()) { + // Strip all modifiers. This is safe to do since only exact keyCode + metaState + // modifiers will trigger a fallback. + // E.g. Ctrl + Space -> language_switch (fallback generated) + // Ctrl + Alt + Space -> Ctrl + Alt + Space (no fallback generated) + action.metaState = 0; + } else { + action.metaState = KeyEvent.normalizeMetaState(action.metaState); + } return action; } action.recycle(); diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index 6e6e87bb9403..4fc1cfc0ca82 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -206,7 +206,8 @@ public class Surface implements Parcelable { @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"FRAME_RATE_COMPATIBILITY_"}, value = {FRAME_RATE_COMPATIBILITY_DEFAULT, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, - FRAME_RATE_COMPATIBILITY_GTE}) + FRAME_RATE_COMPATIBILITY_AT_LEAST, FRAME_RATE_COMPATIBILITY_EXACT, + FRAME_RATE_COMPATIBILITY_MIN}) public @interface FrameRateCompatibility {} // From native_window.h. Keep these in sync. @@ -219,7 +220,7 @@ public class Surface implements Parcelable { * In Android version {@link Build.VERSION_CODES#BAKLAVA} and above, use * {@link FRAME_RATE_COMPATIBILITY_DEFAULT} for game content. * For other cases, see {@link FRAME_RATE_COMPATIBILITY_FIXED_SOURCE} and - * {@link FRAME_RATE_COMPATIBILITY_GTE}. + * {@link FRAME_RATE_COMPATIBILITY_AT_LEAST}. */ public static final int FRAME_RATE_COMPATIBILITY_DEFAULT = 0; @@ -234,7 +235,7 @@ public class Surface implements Parcelable { public static final int FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1; /** - * The surface requests a frame rate that is greater than or equal to the specified frame rate. + * The surface requests a frame rate that is at least the specified frame rate. * This value should be used for UIs, animations, scrolling and fling, and anything that is not * a game or video. * @@ -242,7 +243,7 @@ public class Surface implements Parcelable { * {@link FRAME_RATE_COMPATIBILITY_DEFAULT}. */ @FlaggedApi(com.android.graphics.surfaceflinger.flags.Flags.FLAG_ARR_SETFRAMERATE_GTE_ENUM) - public static final int FRAME_RATE_COMPATIBILITY_GTE = 2; + public static final int FRAME_RATE_COMPATIBILITY_AT_LEAST = 2; /** * This surface belongs to an app on the High Refresh Rate Deny list, and needs the display diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index f22505b80948..833f2d98554e 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -22,7 +22,6 @@ import static android.graphics.Matrix.MSKEW_X; import static android.graphics.Matrix.MSKEW_Y; import static android.graphics.Matrix.MTRANS_X; import static android.graphics.Matrix.MTRANS_Y; -import static android.view.flags.Flags.bufferStuffingRecovery; import static android.view.SurfaceControlProto.HASH_CODE; import static android.view.SurfaceControlProto.LAYER_ID; import static android.view.SurfaceControlProto.NAME; @@ -5118,11 +5117,9 @@ public final class SurfaceControl implements Parcelable { */ @NonNull public Transaction setRecoverableFromBufferStuffing(@NonNull SurfaceControl sc) { - if (bufferStuffingRecovery()) { - checkPreconditions(sc); - nativeSetFlags(mNativeObject, sc.mNativeObject, RECOVERABLE_FROM_BUFFER_STUFFING, - RECOVERABLE_FROM_BUFFER_STUFFING); - } + checkPreconditions(sc); + nativeSetFlags(mNativeObject, sc.mNativeObject, RECOVERABLE_FROM_BUFFER_STUFFING, + RECOVERABLE_FROM_BUFFER_STUFFING); return this; } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index d13f0e21bf80..8f8bfe2865a9 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -27,7 +27,7 @@ import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE; -import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE; +import static android.view.Surface.FRAME_RATE_COMPATIBILITY_AT_LEAST; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED; import static android.view.accessibility.Flags.FLAG_DEPRECATE_ACCESSIBILITY_ANNOUNCEMENT_APIS; @@ -34251,7 +34251,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, compatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE; frameRateToSet = frameRate; } else { - compatibility = FRAME_RATE_COMPATIBILITY_GTE; + compatibility = FRAME_RATE_COMPATIBILITY_AT_LEAST; frameRateToSet = velocityFrameRate; } viewRootImpl.votePreferredFrameRate(frameRateToSet, compatibility); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 1d27574eca8c..16cdb64f62cc 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -38,7 +38,7 @@ import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE; -import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE; +import static android.view.Surface.FRAME_RATE_COMPATIBILITY_AT_LEAST; import static android.view.View.FRAME_RATE_CATEGORY_REASON_BOOST; import static android.view.View.FRAME_RATE_CATEGORY_REASON_CONFLICTED; import static android.view.View.FRAME_RATE_CATEGORY_REASON_INTERMITTENT; @@ -1828,7 +1828,8 @@ public final class ViewRootImpl implements ViewParent, | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_STATE | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED : DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED - | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED; DisplayManagerGlobal .getInstance() @@ -13271,7 +13272,7 @@ public final class ViewRootImpl implements ViewParent, * We set category to HIGH if the maximum frame rate is greater than 60. * Otherwise, we set category to NORMAL. * - * Use FRAME_RATE_COMPATIBILITY_GTE for velocity and FRAME_RATE_COMPATIBILITY_FIXED_SOURCE + * Use FRAME_RATE_COMPATIBILITY_AT_LEAST for velocity and FRAME_RATE_COMPATIBILITY_FIXED_SOURCE * for TextureView video play and user requested frame rate. * * @param frameRate the preferred frame rate of a View @@ -13282,7 +13283,7 @@ public final class ViewRootImpl implements ViewParent, if (frameRate <= 0) { return; } - if (frameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE && !mIsPressedGesture) { + if (frameRateCompatibility == FRAME_RATE_COMPATIBILITY_AT_LEAST && !mIsPressedGesture) { mIsTouchBoosting = false; mIsFrameRateBoosting = false; if (!sToolkitFrameRateVelocityMappingReadOnlyFlagValue) { diff --git a/core/java/android/view/contentcapture/ChildContentCaptureSession.java b/core/java/android/view/contentcapture/ChildContentCaptureSession.java index 70c899f1efc7..8baa55f8e377 100644 --- a/core/java/android/view/contentcapture/ChildContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ChildContentCaptureSession.java @@ -142,6 +142,11 @@ final class ChildContentCaptureSession extends ContentCaptureSession { } @Override + void internalNotifySessionFlushEvent(int sessionId) { + getMainCaptureSession().internalNotifySessionFlushEvent(sessionId); + } + + @Override boolean isContentCaptureEnabled() { return getMainCaptureSession().isContentCaptureEnabled(); } diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java index db4ac5de0b49..efd39163c3b8 100644 --- a/core/java/android/view/contentcapture/ContentCaptureEvent.java +++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java @@ -18,7 +18,9 @@ package android.view.contentcapture; import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString; import static android.view.contentcapture.ContentCaptureManager.DEBUG; import static android.view.contentcapture.ContentCaptureManager.NO_SESSION_ID; +import static android.view.contentcapture.flags.Flags.FLAG_CCAPI_BAKLAVA_ENABLED; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -137,6 +139,12 @@ public final class ContentCaptureEvent implements Parcelable { */ public static final int TYPE_WINDOW_BOUNDS_CHANGED = 10; + /** + * Called to flush a semantics meaningful view changes status to Intelligence Service. + */ + @FlaggedApi(FLAG_CCAPI_BAKLAVA_ENABLED) + public static final int TYPE_SESSION_FLUSH = 11; + /** @hide */ @IntDef(prefix = { "TYPE_" }, value = { TYPE_VIEW_APPEARED, @@ -149,6 +157,7 @@ public final class ContentCaptureEvent implements Parcelable { TYPE_SESSION_RESUMED, TYPE_VIEW_INSETS_CHANGED, TYPE_WINDOW_BOUNDS_CHANGED, + TYPE_SESSION_FLUSH, }) @Retention(RetentionPolicy.SOURCE) public @interface EventType{} @@ -697,6 +706,8 @@ public final class ContentCaptureEvent implements Parcelable { return "VIEW_INSETS_CHANGED"; case TYPE_WINDOW_BOUNDS_CHANGED: return "TYPE_WINDOW_BOUNDS_CHANGED"; + case TYPE_SESSION_FLUSH: + return "TYPE_SESSION_FLUSH"; default: return "UKNOWN_TYPE: " + type; } diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index 0ca36ba28e3a..9aeec20ec9b7 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -19,8 +19,10 @@ import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static android.view.contentcapture.ContentCaptureHelper.sDebug; import static android.view.contentcapture.ContentCaptureHelper.sVerbose; import static android.view.contentcapture.ContentCaptureManager.NO_SESSION_ID; +import static android.view.contentcapture.flags.Flags.FLAG_CCAPI_BAKLAVA_ENABLED; import android.annotation.CallSuper; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -548,6 +550,35 @@ public abstract class ContentCaptureSession implements AutoCloseable { abstract void internalNotifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets); + /** + * Flushes an internal buffer of UI events and signals System Intelligence (SI) that a + * semantically meaningful state has been reached. SI uses this signal to potentially + * rebuild the view hierarchy and understand the current state of the UI. + * + * <p>UI events are often batched together for performance reasons. A semantic batch + * represents a series of events that, when applied sequentially, result in a + * meaningful and complete UI state. + * + * <p>It is crucial to call {@code flush()} after completing a semantic batch to ensure + * SI can accurately reconstruct the view hierarchy. + * + * <p><b>Premature Flushing:</b> Calling {@code flush()} within a semantic batch may + * lead to SI failing to rebuild the view hierarchy correctly. This could manifest as + * incorrect ordering of sibling nodes. + * + * <p><b>Delayed Flushing:</b> While not immediately flushing after a semantic batch is + * generally safe, it's recommended to do so as soon as possible. In the worst-case + * scenario where a {@code flush()} is never called, SI will attempt to process the + * events after a short delay based on view appearance and disappearance events. + */ + @FlaggedApi(FLAG_CCAPI_BAKLAVA_ENABLED) + public void flush() { + internalNotifySessionFlushEvent(mId); + } + + /** @hide */ + abstract void internalNotifySessionFlushEvent(int sessionId); + /** @hide */ public void notifyViewTreeEvent(boolean started) { internalNotifyViewTreeEvent(mId, started); diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index eb827dd5258d..2fb78c038ca2 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -17,6 +17,7 @@ package android.view.contentcapture; import static android.view.contentcapture.ContentCaptureEvent.TYPE_CONTEXT_UPDATED; import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_FINISHED; +import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_FLUSH; import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_PAUSED; import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_RESUMED; import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED; @@ -623,6 +624,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) @Override public void flush(@FlushReason int reason) { + // TODO: b/380381249 renaming the internal APIs to prevent confusions between this and the + // public API. runOnContentCaptureThread(() -> flushImpl(reason)); } @@ -890,6 +893,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { enqueueEvent(event); } + @Override + void internalNotifySessionFlushEvent(int sessionId) { + final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_SESSION_FLUSH); + enqueueEvent(event, FORCE_FLUSH); + } + private List<ContentCaptureEvent> clearBufferEvents() { final ArrayList<ContentCaptureEvent> bufferEvents = new ArrayList<>(); ContentCaptureEvent event; diff --git a/core/java/android/view/contentcapture/OWNERS b/core/java/android/view/contentcapture/OWNERS index 9ac273f515e7..30f4cae4bf19 100644 --- a/core/java/android/view/contentcapture/OWNERS +++ b/core/java/android/view/contentcapture/OWNERS @@ -1,4 +1,5 @@ # Bug component: 544200 -hackz@google.com -shivanker@google.com +dariofreni@google.com +klikli@google.com +shikhamalhotra@google.com diff --git a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig index 416a877d87ab..f709ed7f57cd 100644 --- a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig +++ b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig @@ -7,3 +7,10 @@ flag { description: "Feature flag for running content capture tasks on background thread" bug: "309411951" } + +flag { + name: "ccapi_baklava_enabled" + namespace: "machine_learning" + description: "Feature flag for baklava content capture API" + bug: "380381249" +} diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java index 5397da11eb36..435c8c79122f 100644 --- a/core/java/android/window/SnapshotDrawerUtils.java +++ b/core/java/android/window/SnapshotDrawerUtils.java @@ -44,20 +44,16 @@ import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_AT import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES; import static com.android.internal.policy.DecorView.getNavigationBarRect; -import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityThread; import android.content.Context; import android.graphics.Canvas; -import android.graphics.GraphicBuffer; import android.graphics.Paint; -import android.graphics.PixelFormat; import android.graphics.Rect; import android.hardware.HardwareBuffer; import android.os.IBinder; import android.util.Log; -import android.view.InsetsState; import android.view.SurfaceControl; import android.view.ViewGroup; import android.view.WindowInsets; @@ -66,7 +62,6 @@ import android.view.WindowManager; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.DecorView; -import com.android.window.flags.Flags; /** * Utils class to help draw a snapshot on a surface. @@ -103,8 +98,6 @@ public class SnapshotDrawerUtils { | FLAG_SECURE | FLAG_DIM_BEHIND; - private static final Paint sBackgroundPaint = new Paint(); - /** * The internal object to hold the surface and drawing on it. */ @@ -115,54 +108,29 @@ public class SnapshotDrawerUtils { private final TaskSnapshot mSnapshot; private final CharSequence mTitle; - private SystemBarBackgroundPainter mSystemBarBackgroundPainter; - private final Rect mFrame = new Rect(); - private final Rect mSystemBarInsets = new Rect(); private final int mSnapshotW; private final int mSnapshotH; - private boolean mSizeMismatch; + private final int mContainerW; + private final int mContainerH; public SnapshotSurface(SurfaceControl rootSurface, TaskSnapshot snapshot, - CharSequence title) { + Rect windowBounds, CharSequence title) { mRootSurface = rootSurface; mSnapshot = snapshot; mTitle = title; final HardwareBuffer hwBuffer = snapshot.getHardwareBuffer(); mSnapshotW = hwBuffer.getWidth(); mSnapshotH = hwBuffer.getHeight(); + mContainerW = windowBounds.width(); + mContainerH = windowBounds.height(); } - /** - * Initiate system bar painter to draw the system bar background. - */ - @VisibleForTesting - public void initiateSystemBarPainter(int windowFlags, int windowPrivateFlags, - int appearance, ActivityManager.TaskDescription taskDescription, - @WindowInsets.Type.InsetsType int requestedVisibleTypes) { - mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags, - windowPrivateFlags, appearance, taskDescription, 1f, requestedVisibleTypes); - int backgroundColor = taskDescription.getBackgroundColor(); - sBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE); - } - - /** - * Set frame size that the snapshot should fill. It is the bounds of a task or activity. - */ - @VisibleForTesting - public void setFrames(Rect frame, Rect systemBarInsets) { - mFrame.set(frame); + private void drawSnapshot(boolean releaseAfterDraw) { final Rect letterboxInsets = mSnapshot.getLetterboxInsets(); - mSizeMismatch = (mFrame.width() != mSnapshotW || mFrame.height() != mSnapshotH) + final boolean sizeMismatch = mContainerW != mSnapshotW || mContainerH != mSnapshotH || letterboxInsets.left != 0 || letterboxInsets.top != 0; - if (!Flags.drawSnapshotAspectRatioMatch() && systemBarInsets != null) { - mSystemBarInsets.set(systemBarInsets); - mSystemBarBackgroundPainter.setInsets(systemBarInsets); - } - } - - private void drawSnapshot(boolean releaseAfterDraw) { - Log.v(TAG, "Drawing snapshot surface sizeMismatch=" + mSizeMismatch); - if (mSizeMismatch) { + Log.v(TAG, "Drawing snapshot surface sizeMismatch=" + sizeMismatch); + if (sizeMismatch) { // The dimensions of the buffer and the window don't match, so attaching the buffer // will fail. Better create a child window with the exact dimensions and fill the // parent window with the background color! @@ -189,11 +157,6 @@ public class SnapshotDrawerUtils { private void drawSizeMismatchSnapshot() { final HardwareBuffer buffer = mSnapshot.getHardwareBuffer(); - // We consider nearly matched dimensions as there can be rounding errors and the user - // won't notice very minute differences from scaling one dimension more than the other - boolean aspectRatioMismatch = !isAspectRatioMatch(mFrame, mSnapshotW, mSnapshotH) - && !Flags.drawSnapshotAspectRatioMatch(); - // Keep a reference to it such that it doesn't get destroyed when finalized. SurfaceControl childSurfaceControl = new SurfaceControl.Builder() .setName(mTitle + " - task-snapshot-surface") @@ -203,166 +166,28 @@ public class SnapshotDrawerUtils { .setCallsite("TaskSnapshotWindow.drawSizeMismatchSnapshot") .build(); - final Rect frame; final Rect letterboxInsets = mSnapshot.getLetterboxInsets(); float offsetX = letterboxInsets.left; float offsetY = letterboxInsets.top; // We can just show the surface here as it will still be hidden as the parent is // still hidden. mTransaction.show(childSurfaceControl); - if (aspectRatioMismatch) { - Rect crop = null; - if (letterboxInsets.left != 0 || letterboxInsets.top != 0 - || letterboxInsets.right != 0 || letterboxInsets.bottom != 0) { - // Clip off letterbox. - crop = calculateSnapshotCrop(letterboxInsets); - // If the snapshot can cover the frame, then no need to draw background. - aspectRatioMismatch = !isAspectRatioMatch(mFrame, crop); - } - // if letterbox doesn't match window frame, try crop by content insets - if (aspectRatioMismatch) { - // Clip off ugly navigation bar. - final Rect contentInsets = mSnapshot.getContentInsets(); - crop = calculateSnapshotCrop(contentInsets); - offsetX = contentInsets.left; - offsetY = contentInsets.top; - } - frame = calculateSnapshotFrame(crop); - mTransaction.setCrop(childSurfaceControl, crop); - } else { - frame = null; - } // Align the snapshot with content area. if (offsetX != 0f || offsetY != 0f) { mTransaction.setPosition(childSurfaceControl, - -offsetX * mFrame.width() / mSnapshot.getTaskSize().x, - -offsetY * mFrame.height() / mSnapshot.getTaskSize().y); + -offsetX * mContainerW / mSnapshot.getTaskSize().x, + -offsetY * mContainerH / mSnapshot.getTaskSize().y); } // Scale the mismatch dimensions to fill the target frame. - final float scaleX = (float) mFrame.width() / mSnapshotW; - final float scaleY = (float) mFrame.height() / mSnapshotH; + final float scaleX = (float) mContainerW / mSnapshotW; + final float scaleY = (float) mContainerH / mSnapshotH; mTransaction.setScale(childSurfaceControl, scaleX, scaleY); mTransaction.setColorSpace(childSurfaceControl, mSnapshot.getColorSpace()); mTransaction.setBuffer(childSurfaceControl, mSnapshot.getHardwareBuffer()); - - if (aspectRatioMismatch) { - GraphicBuffer background = GraphicBuffer.create(mFrame.width(), mFrame.height(), - PixelFormat.RGBA_8888, - GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER - | GraphicBuffer.USAGE_SW_WRITE_RARELY); - final Canvas c = background != null ? background.lockCanvas() : null; - if (c == null) { - Log.e(TAG, "Unable to draw snapshot: failed to allocate graphic buffer for " - + mTitle); - mTransaction.clear(); - childSurfaceControl.release(); - return; - } - drawBackgroundAndBars(c, frame); - background.unlockCanvasAndPost(c); - mTransaction.setBuffer(mRootSurface, - HardwareBuffer.createFromGraphicBuffer(background)); - } mTransaction.apply(); childSurfaceControl.release(); } - - /** - * Calculates the snapshot crop in snapshot coordinate space. - * @param insets Content insets or Letterbox insets - * @return crop rect in snapshot coordinate space. - */ - @VisibleForTesting - public Rect calculateSnapshotCrop(@NonNull Rect insets) { - final Rect rect = new Rect(); - rect.set(0, 0, mSnapshotW, mSnapshotH); - - final float scaleX = (float) mSnapshotW / mSnapshot.getTaskSize().x; - final float scaleY = (float) mSnapshotH / mSnapshot.getTaskSize().y; - - // Let's remove all system decorations except the status bar, but only if the task is at - // the very top of the screen. - final boolean isTop = mFrame.top == 0; - rect.inset((int) (insets.left * scaleX), - isTop ? 0 : (int) (insets.top * scaleY), - (int) (insets.right * scaleX), - (int) (insets.bottom * scaleY)); - return rect; - } - - /** - * Calculates the snapshot frame in window coordinate space from crop. - * - * @param crop rect that is in snapshot coordinate space. - */ - @VisibleForTesting - public Rect calculateSnapshotFrame(Rect crop) { - final float scaleX = (float) mSnapshotW / mSnapshot.getTaskSize().x; - final float scaleY = (float) mSnapshotH / mSnapshot.getTaskSize().y; - - // Rescale the frame from snapshot to window coordinate space - final Rect frame = new Rect(0, 0, - (int) (crop.width() / scaleX + 0.5f), - (int) (crop.height() / scaleY + 0.5f) - ); - - // However, we also need to make space for the navigation bar on the left side. - frame.offset(mSystemBarInsets.left, 0); - return frame; - } - - /** - * Draw status bar and navigation bar background. - */ - @VisibleForTesting - public void drawBackgroundAndBars(Canvas c, Rect frame) { - final int statusBarHeight = mSystemBarBackgroundPainter.getStatusBarColorViewHeight(); - final boolean fillHorizontally = c.getWidth() > frame.right; - final boolean fillVertically = c.getHeight() > frame.bottom; - if (fillHorizontally) { - c.drawRect(frame.right, alpha(mSystemBarBackgroundPainter.mStatusBarColor) == 0xFF - ? statusBarHeight : 0, c.getWidth(), fillVertically - ? frame.bottom : c.getHeight(), sBackgroundPaint); - } - if (fillVertically) { - c.drawRect(0, frame.bottom, c.getWidth(), c.getHeight(), sBackgroundPaint); - } - mSystemBarBackgroundPainter.drawDecors(c, frame); - } - - /** - * Ask system bar background painter to draw status bar background. - */ - @VisibleForTesting - public void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame) { - mSystemBarBackgroundPainter.drawStatusBarBackground(c, alreadyDrawnFrame, - mSystemBarBackgroundPainter.getStatusBarColorViewHeight()); - } - - /** - * Ask system bar background painter to draw navigation bar background. - */ - @VisibleForTesting - public void drawNavigationBarBackground(Canvas c) { - mSystemBarBackgroundPainter.drawNavigationBarBackground(c); - } - } - - private static boolean isAspectRatioMatch(Rect frame, int w, int h) { - if (frame.isEmpty()) { - return false; - } - return Math.abs(((float) w / h) - ((float) frame.width() / frame.height())) <= 0.01f; - } - - private static boolean isAspectRatioMatch(Rect frame1, Rect frame2) { - if (frame1.isEmpty() || frame2.isEmpty()) { - return false; - } - return Math.abs( - ((float) frame2.width() / frame2.height()) - - ((float) frame1.width() / frame1.height())) <= 0.01f; } /** @@ -383,28 +208,15 @@ public class SnapshotDrawerUtils { /** * Help method to draw the snapshot on a surface. */ - public static void drawSnapshotOnSurface(StartingWindowInfo info, WindowManager.LayoutParams lp, + public static void drawSnapshotOnSurface(WindowManager.LayoutParams lp, SurfaceControl rootSurface, TaskSnapshot snapshot, - Rect windowBounds, InsetsState topWindowInsetsState, - boolean releaseAfterDraw) { + Rect windowBounds, boolean releaseAfterDraw) { if (windowBounds.isEmpty()) { Log.e(TAG, "Unable to draw snapshot on an empty windowBounds"); return; } final SnapshotSurface drawSurface = new SnapshotSurface( - rootSurface, snapshot, lp.getTitle()); - final WindowManager.LayoutParams attrs = Flags.drawSnapshotAspectRatioMatch() - ? info.mainWindowLayoutParams : info.topOpaqueWindowLayoutParams; - final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo; - final ActivityManager.TaskDescription taskDescription = - getOrCreateTaskDescription(runningTaskInfo); - Rect systemBarInsets = null; - if (!Flags.drawSnapshotAspectRatioMatch()) { - drawSurface.initiateSystemBarPainter(lp.flags, lp.privateFlags, - attrs.insetsFlags.appearance, taskDescription, info.requestedVisibleTypes); - systemBarInsets = getSystemBarInsets(windowBounds, topWindowInsetsState); - } - drawSurface.setFrames(windowBounds, systemBarInsets); + rootSurface, snapshot, windowBounds, lp.getTitle()); drawSurface.drawSnapshot(releaseAfterDraw); } @@ -414,10 +226,8 @@ public class SnapshotDrawerUtils { public static WindowManager.LayoutParams createLayoutParameters(StartingWindowInfo info, CharSequence title, @WindowManager.LayoutParams.WindowType int windowType, int pixelFormat, IBinder token) { - final WindowManager.LayoutParams attrs = Flags.drawSnapshotAspectRatioMatch() - ? info.mainWindowLayoutParams : info.topOpaqueWindowLayoutParams; - final WindowManager.LayoutParams mainWindowParams = info.mainWindowLayoutParams; - if (attrs == null || mainWindowParams == null) { + final WindowManager.LayoutParams attrs = info.mainWindowLayoutParams; + if (attrs == null) { Log.w(TAG, "unable to create taskSnapshot surface "); return null; } @@ -427,9 +237,9 @@ public class SnapshotDrawerUtils { final int windowFlags = attrs.flags; final int windowPrivateFlags = attrs.privateFlags; - layoutParams.packageName = mainWindowParams.packageName; - layoutParams.windowAnimations = mainWindowParams.windowAnimations; - layoutParams.dimAmount = mainWindowParams.dimAmount; + layoutParams.packageName = attrs.packageName; + layoutParams.windowAnimations = attrs.windowAnimations; + layoutParams.dimAmount = attrs.dimAmount; layoutParams.type = windowType; layoutParams.format = pixelFormat; layoutParams.flags = (windowFlags & ~FLAG_INHERIT_EXCLUDES) @@ -460,14 +270,6 @@ public class SnapshotDrawerUtils { return layoutParams; } - static Rect getSystemBarInsets(Rect frame, @Nullable InsetsState state) { - if (state == null) { - return new Rect(); - } - return state.calculateInsets(frame, WindowInsets.Type.systemBars(), - false /* ignoreVisibility */).toRect(); - } - /** * Helper class to draw the background of the system bars in regions the task snapshot isn't * filling the window. diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java index 72df343a2dbe..80ccbddbd1d9 100644 --- a/core/java/android/window/StartingWindowInfo.java +++ b/core/java/android/window/StartingWindowInfo.java @@ -27,7 +27,6 @@ import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; -import android.view.InsetsState; import android.view.SurfaceControl; import android.view.WindowInsets; import android.view.WindowInsets.Type.InsetsType; @@ -100,20 +99,6 @@ public final class StartingWindowInfo implements Parcelable { public ActivityInfo targetActivityInfo; /** - * InsetsState of TopFullscreenOpaqueWindow - * @hide - */ - @Nullable - public InsetsState topOpaqueWindowInsetsState; - - /** - * LayoutParams of TopFullscreenOpaqueWindow - * @hide - */ - @Nullable - public WindowManager.LayoutParams topOpaqueWindowLayoutParams; - - /** * LayoutParams of MainWindow * @hide */ @@ -263,8 +248,6 @@ public final class StartingWindowInfo implements Parcelable { taskBounds.writeToParcel(dest, flags); dest.writeTypedObject(targetActivityInfo, flags); dest.writeInt(startingWindowTypeParameter); - dest.writeTypedObject(topOpaqueWindowInsetsState, flags); - dest.writeTypedObject(topOpaqueWindowLayoutParams, flags); dest.writeTypedObject(mainWindowLayoutParams, flags); dest.writeInt(splashScreenThemeResId); dest.writeBoolean(isKeyguardOccluded); @@ -280,9 +263,6 @@ public final class StartingWindowInfo implements Parcelable { taskBounds.readFromParcel(source); targetActivityInfo = source.readTypedObject(ActivityInfo.CREATOR); startingWindowTypeParameter = source.readInt(); - topOpaqueWindowInsetsState = source.readTypedObject(InsetsState.CREATOR); - topOpaqueWindowLayoutParams = source.readTypedObject( - WindowManager.LayoutParams.CREATOR); mainWindowLayoutParams = source.readTypedObject(WindowManager.LayoutParams.CREATOR); splashScreenThemeResId = source.readInt(); isKeyguardOccluded = source.readBoolean(); @@ -302,8 +282,6 @@ public final class StartingWindowInfo implements Parcelable { + " topActivityType=" + taskInfo.topActivityType + " preferredStartingWindowType=" + Integer.toHexString(startingWindowTypeParameter) - + " insetsState=" + topOpaqueWindowInsetsState - + " topWindowLayoutParams=" + topOpaqueWindowLayoutParams + " mainWindowLayoutParams=" + mainWindowLayoutParams + " splashScreenThemeResId " + Integer.toHexString(splashScreenThemeResId); } diff --git a/core/java/android/window/WindowTokenClientController.java b/core/java/android/window/WindowTokenClientController.java index 1ec05b65861d..fcd7dfbb1769 100644 --- a/core/java/android/window/WindowTokenClientController.java +++ b/core/java/android/window/WindowTokenClientController.java @@ -35,6 +35,7 @@ import android.view.WindowManagerGlobal; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.window.flags.Flags; /** * Singleton controller to manage the attached {@link WindowTokenClient}s, and to dispatch @@ -137,7 +138,9 @@ public class WindowTokenClientController { // is initialized later, the SystemUiContext will start reporting from // DisplayContent#registerSystemUiContext, and WindowTokenClientController can report // the Configuration to the correct client. - recordWindowContextToken(client); + if (Flags.trackSystemUiContextBeforeWms()) { + recordWindowContextToken(client); + } return false; } final WindowContextInfo info; @@ -145,6 +148,9 @@ public class WindowTokenClientController { info = wms.attachWindowContextToDisplayContent(mAppThread, client, displayId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); + } catch (Exception e) { + Log.e(TAG, "Failed attachToDisplayContent", e); + return false; } if (info == null) { return false; diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig index 801698caff0e..0d04961569e6 100644 --- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig +++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig @@ -58,13 +58,6 @@ flag { } flag { - name: "user_min_aspect_ratio_app_default" - namespace: "large_screen_experiences_app_compat" - description: "Whether the API PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT is available" - bug: "310816437" -} - -flag { name: "allow_hide_scm_button" namespace: "large_screen_experiences_app_compat" description: "Whether we should allow hiding the size compat restart button" diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 30668a6c6b1d..9d11d149b0ed 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -243,17 +243,6 @@ flag { } flag { - name: "draw_snapshot_aspect_ratio_match" - namespace: "windowing_frontend" - description: "The aspect ratio should always match when drawing snapshot" - bug: "341020277" - is_fixed_read_only: true - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "system_ui_post_animation_end" namespace: "windowing_frontend" description: "Run AnimatorListener#onAnimationEnd on next frame for SystemUI" diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index 5a092b8522db..abd93cfaf179 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -105,6 +105,13 @@ flag { flag { namespace: "windowing_sdk" + name: "activity_embedding_support_for_connected_displays" + description: "Enables activity embedding support for connected displays, including enabling AE optimization for Settings." + bug: "369438353" +} + +flag { + namespace: "windowing_sdk" name: "wlinfo_oncreate" description: "Makes WindowLayoutInfo accessible without racing in the Activity#onCreate()" bug: "337820752" @@ -138,3 +145,23 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + namespace: "windowing_sdk" + name: "normalize_home_intent" + description: "To ensure home is started in correct intent" + bug: "378505461" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + namespace: "windowing_sdk" + name: "condense_configuration_change_for_simple_mode" + description: "Condense configuration change for simple mode" + bug: "356738240" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/com/android/internal/jank/DisplayResolutionTracker.java b/core/java/com/android/internal/jank/DisplayResolutionTracker.java index 0c2fd4bbd7ae..5d66b3c10197 100644 --- a/core/java/com/android/internal/jank/DisplayResolutionTracker.java +++ b/core/java/com/android/internal/jank/DisplayResolutionTracker.java @@ -148,8 +148,8 @@ public class DisplayResolutionTracker { public void registerDisplayListener(DisplayManager.DisplayListener listener) { manager.registerDisplayListener(listener, handler, DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED - | DisplayManagerGlobal - .INTERNAL_EVENT_FLAG_DISPLAY_CHANGED, + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE, ActivityThread.currentPackageName()); } diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 3e2f30118b2a..f14e1f63cdf6 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -236,4 +236,7 @@ interface IStatusBarService /** Shows rear display educational dialog */ void showRearDisplayDialog(int currentBaseState); + + /** Unbundle a categorized notification */ + void unbundleNotification(String key); } diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 9bd52372e6c4..39ddea614ee4 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -25,6 +25,8 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED import static android.security.Flags.reportPrimaryAuthAttempts; import static android.security.Flags.shouldTrustManagerListenForPrimaryAuth; +import static com.android.internal.widget.flags.Flags.hideLastCharWithPhysicalInput; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -42,6 +44,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.UserInfo; +import android.hardware.input.InputManagerGlobal; import android.os.Build; import android.os.Handler; import android.os.Looper; @@ -59,6 +62,7 @@ import android.util.Log; import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.util.SparseLongArray; +import android.view.InputDevice; import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalServices; @@ -1097,12 +1101,20 @@ public class LockPatternUtils { return type == CREDENTIAL_TYPE_PATTERN; } + private boolean hasActivePointerDeviceAttached() { + return !getEnabledNonTouchInputDevices(InputDevice.SOURCE_CLASS_POINTER).isEmpty(); + } + /** * @return Whether the visible pattern is enabled. */ @UnsupportedAppUsage public boolean isVisiblePatternEnabled(int userId) { - return getBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, true, userId); + boolean defaultValue = true; + if (hideLastCharWithPhysicalInput()) { + defaultValue = !hasActivePointerDeviceAttached(); + } + return getBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, defaultValue, userId); } /** @@ -1116,11 +1128,39 @@ public class LockPatternUtils { return getString(Settings.Secure.LOCK_PATTERN_VISIBLE, userId) != null; } + private List<InputDevice> getEnabledNonTouchInputDevices(int source) { + final InputManagerGlobal inputManager = InputManagerGlobal.getInstance(); + final int[] inputIds = inputManager.getInputDeviceIds(); + List<InputDevice> matchingDevices = new ArrayList<InputDevice>(); + for (final int deviceId : inputIds) { + final InputDevice inputDevice = inputManager.getInputDevice(deviceId); + if (!inputDevice.isEnabled()) continue; + if (inputDevice.supportsSource(InputDevice.SOURCE_TOUCHSCREEN)) continue; + if (inputDevice.isVirtual()) continue; + if (!inputDevice.supportsSource(source)) continue; + matchingDevices.add(inputDevice); + } + return matchingDevices; + } + + private boolean hasPhysicalKeyboardActive() { + final List<InputDevice> keyboards = + getEnabledNonTouchInputDevices(InputDevice.SOURCE_KEYBOARD); + for (final InputDevice keyboard : keyboards) { + if (keyboard.isFullKeyboard()) return true; + } + return false; + } + /** * @return Whether enhanced pin privacy is enabled. */ public boolean isPinEnhancedPrivacyEnabled(int userId) { - return getBoolean(LOCK_PIN_ENHANCED_PRIVACY, false, userId); + boolean defaultValue = false; + if (hideLastCharWithPhysicalInput()) { + defaultValue = hasPhysicalKeyboardActive(); + } + return getBoolean(LOCK_PIN_ENHANCED_PRIVACY, defaultValue, userId); } /** diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 027113a75f6b..5acdf32a9f84 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -482,11 +482,22 @@ cc_library_shared_for_libandroid_runtime { "libbinder", "libhidlbase", // libhwbinder is in here ], + version_script: "platform/linux/libandroid_runtime_export.txt", + }, + darwin: { + host_ldlibs: [ + "-framework AppKit", + ], + dist: { + targets: ["layoutlib_jni"], + dir: "layoutlib_native/darwin", + }, + exported_symbols_list: "platform/darwin/libandroid_runtime_export.exp", }, linux_glibc_x86_64: { ldflags: ["-static-libgcc"], dist: { - targets: ["layoutlib"], + targets: ["layoutlib_jni"], dir: "layoutlib_native/linux", tag: "stripped_all", }, diff --git a/core/jni/android_hardware_camera2_CameraDevice.cpp b/core/jni/android_hardware_camera2_CameraDevice.cpp index 493c7073416c..04cfed581750 100644 --- a/core/jni/android_hardware_camera2_CameraDevice.cpp +++ b/core/jni/android_hardware_camera2_CameraDevice.cpp @@ -30,6 +30,7 @@ #include <nativehelper/JNIHelp.h> #include "android_os_Parcel.h" #include "core_jni_helpers.h" +#include <android/binder_auto_utils.h> #include <android/binder_parcel_jni.h> #include <android/hardware/camera2/ICameraDeviceUser.h> #include <aidl/android/hardware/common/fmq/MQDescriptor.h> @@ -40,6 +41,7 @@ using namespace android; using ::android::AidlMessageQueue; +using ndk::ScopedAParcel; using ResultMetadataQueue = AidlMessageQueue<int8_t, SynchronizedReadWrite>; class FMQReader { @@ -75,15 +77,16 @@ extern "C" { static jlong CameraDevice_createFMQReader(JNIEnv *env, jclass thiz, jobject resultParcel) { - AParcel *resultAParcel = AParcel_fromJavaParcel(env, resultParcel); - if (resultAParcel == nullptr) { + ScopedAParcel sResultAParcel(AParcel_fromJavaParcel(env, resultParcel)); + if (sResultAParcel.get() == nullptr) { ALOGE("%s: Error creating result parcel", __FUNCTION__); return 0; } - AParcel_setDataPosition(resultAParcel, 0); + + AParcel_setDataPosition(sResultAParcel.get(), 0); MQDescriptor<int8_t, SynchronizedReadWrite> resultMQ; - if (resultMQ.readFromParcel(resultAParcel) != OK) { + if (resultMQ.readFromParcel(sResultAParcel.get()) != OK) { ALOGE("%s: read from result parcel failed", __FUNCTION__); return 0; } diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index dc7253954d44..67c97258a01d 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -490,6 +490,28 @@ jintArray android_os_Process_getExclusiveCores(JNIEnv* env, jobject clazz) { return cpus; } +jlongArray android_os_Process_getSchedAffinity(JNIEnv* env, jobject clazz, jint pid) { + // sched_getaffinity will do memset 0 for the unset bits within set_alloc_size_byte + cpu_set_t cpu_set; + if (sched_getaffinity(pid, sizeof(cpu_set_t), &cpu_set) != 0) { + signalExceptionForError(env, errno, pid); + return nullptr; + } + int cpu_cnt = std::min(CPU_SETSIZE, get_nprocs_conf()); + int masks_len = (int)(CPU_ALLOC_SIZE(cpu_cnt) / sizeof(__CPU_BITTYPE)); + jlongArray masks = env->NewLongArray(masks_len); + if (masks == nullptr) { + jniThrowException(env, "java/lang/OutOfMemoryError", nullptr); + return nullptr; + } + jlong* mask_elements = env->GetLongArrayElements(masks, 0); + for (int i = 0; i < masks_len; i++) { + mask_elements[i] = cpu_set.__bits[i]; + } + env->ReleaseLongArrayElements(masks, mask_elements, 0); + return masks; +} + static void android_os_Process_setCanSelfBackground(JNIEnv* env, jobject clazz, jboolean bgOk) { // Establishes the calling thread as illegal to put into the background. // Typically used only for the system process's main looper. @@ -1370,6 +1392,7 @@ static const JNINativeMethod methods[] = { {"getProcessGroup", "(I)I", (void*)android_os_Process_getProcessGroup}, {"createProcessGroup", "(II)I", (void*)android_os_Process_createProcessGroup}, {"getExclusiveCores", "()[I", (void*)android_os_Process_getExclusiveCores}, + {"getSchedAffinity", "(I)[J", (void*)android_os_Process_getSchedAffinity}, {"setArgV0Native", "(Ljava/lang/String;)V", (void*)android_os_Process_setArgV0}, {"setUid", "(I)I", (void*)android_os_Process_setUid}, {"setGid", "(I)I", (void*)android_os_Process_setGid}, diff --git a/core/jni/platform/darwin/libandroid_runtime_export.exp b/core/jni/platform/darwin/libandroid_runtime_export.exp new file mode 100644 index 000000000000..00a7585719ea --- /dev/null +++ b/core/jni/platform/darwin/libandroid_runtime_export.exp @@ -0,0 +1,38 @@ +# +# 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. +# + +# symbols needed for the JNI operations +_JNI_OnLoad +_ANativeWindow* + +# symbols needed to link with layoutlib_jni +___android_log* +__ZNK7android7RefBase* +__ZN7android4base9SetLogger* +__ZN7android4base10SetAborter* +__ZN7android4base11GetProperty* +__ZN7android4Rect* +__ZN7android5Fence* +__ZN7android7RefBase* +__ZN7android7String* +__ZN7android10VectorImpl* +__ZN7android11BufferQueue* +__ZN7android14AndroidRuntime* +__ZN7android14sp_report_raceEv* +__ZN7android15KeyCharacterMap* +__ZN7android15InputDeviceInfo* +__ZN7android31android_view_InputDevice_create* +__ZN7android53android_view_Surface_createFromIGraphicBufferProducer* diff --git a/core/jni/platform/linux/libandroid_runtime_export.txt b/core/jni/platform/linux/libandroid_runtime_export.txt new file mode 100644 index 000000000000..19e3478d5d38 --- /dev/null +++ b/core/jni/platform/linux/libandroid_runtime_export.txt @@ -0,0 +1,43 @@ +# +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +{ + global: + # symbols needed for the JNI operations + JNI_OnLoad; + ANativeWindow*; + + # symbols needed to link with layoutlib_jni + __android_log*; + _ZNK7android7RefBase*; + _ZN7android4base9SetLogger*; + _ZN7android4base10SetAborter*; + _ZN7android4base11GetProperty*; + _ZN7android4Rect*; + _ZN7android5Fence*; + _ZN7android7RefBase*; + _ZN7android7String*; + _ZN7android10VectorImpl*; + _ZN7android11BufferQueue*; + _ZN7android14AndroidRuntime*; + _ZN7android14sp_report_raceEv*; + _ZN7android15KeyCharacterMap*; + _ZN7android15InputDeviceInfo*; + _ZN7android31android_view_InputDevice_create*; + _ZN7android53android_view_Surface_createFromIGraphicBufferProducer*; + local: + *; +}; diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index 7e9d62315ef1..c901ee1e3f8f 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -567,6 +567,8 @@ message SecureSettingsProto { // value. optional SettingProto rtt_calling_mode = 69 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto screen_off_udfps_enabled = 104 [ (android.privacy).dest = DEST_AUTOMATIC ]; + message Screensaver { option (android.msg_privacy).dest = DEST_EXPLICIT; @@ -744,5 +746,5 @@ message SecureSettingsProto { // Please insert fields in alphabetical order and group them into messages // if possible (to avoid reaching the method limit). - // Next tag = 104; + // Next tag = 105; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index e133ca48d0d7..df989527efe4 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -9164,15 +9164,6 @@ </intent-filter> </receiver> - <receiver android:name="com.android.server.updates.CertificateTransparencyLogInstallReceiver" - android:exported="true" - android:permission="android.permission.UPDATE_CONFIG"> - <intent-filter> - <action android:name="android.intent.action.UPDATE_CT_LOGS" /> - <data android:scheme="content" android:host="*" android:mimeType="*/*" /> - </intent-filter> - </receiver> - <receiver android:name="com.android.server.updates.LangIdInstallReceiver" android:exported="true" android:permission="android.permission.UPDATE_CONFIG"> diff --git a/core/res/res/layout/notification_2025_template_collapsed_base.xml b/core/res/res/layout/notification_2025_template_collapsed_base.xml index 76c810bdb2c1..e91e1115ac1c 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_base.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_base.xml @@ -157,39 +157,27 @@ android:maxDrawableHeight="@dimen/notification_right_icon_size" /> - <LinearLayout - android:id="@+id/notification_buttons_column" + <FrameLayout + android:id="@+id/expand_button_touch_container" android:layout_width="wrap_content" android:layout_height="match_parent" - android:layout_alignParentEnd="true" - android:orientation="vertical" + android:minWidth="@dimen/notification_content_margin_end" > - <include layout="@layout/notification_close_button" - android:layout_width="@dimen/notification_close_button_size" - android:layout_height="@dimen/notification_close_button_size" - android:layout_gravity="end" - android:layout_marginEnd="20dp" - /> - - <FrameLayout - android:id="@+id/expand_button_touch_container" + <include layout="@layout/notification_expand_button" android:layout_width="wrap_content" - android:layout_height="0dp" - android:layout_weight="1" - android:minWidth="@dimen/notification_content_margin_end" - > - - <include layout="@layout/notification_expand_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical|end" - /> - - </FrameLayout> + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|end" + /> - </LinearLayout> + </FrameLayout> </LinearLayout> + <include layout="@layout/notification_close_button" + android:id="@+id/close_button" + android:layout_width="@dimen/notification_close_button_size" + android:layout_height="@dimen/notification_close_button_size" + android:layout_gravity="top|end" /> + </FrameLayout> diff --git a/core/res/res/layout/notification_2025_template_collapsed_media.xml b/core/res/res/layout/notification_2025_template_collapsed_media.xml index 2e0a7afc3cd1..2d367337bb6f 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_media.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_media.xml @@ -194,4 +194,11 @@ </FrameLayout> </LinearLayout> + + <include layout="@layout/notification_close_button" + android:id="@+id/close_button" + android:layout_width="@dimen/notification_close_button_size" + android:layout_height="@dimen/notification_close_button_size" + android:layout_gravity="top|end" /> + </com.android.internal.widget.MediaNotificationView> diff --git a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml index f644adefda9d..fbecb8c30b9c 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml @@ -199,6 +199,12 @@ </LinearLayout> + <include layout="@layout/notification_close_button" + android:id="@+id/close_button" + android:layout_width="@dimen/notification_close_button_size" + android:layout_height="@dimen/notification_close_button_size" + android:layout_gravity="top|end" /> + </com.android.internal.widget.NotificationMaxHeightFrameLayout> <LinearLayout diff --git a/core/res/res/layout/notification_2025_template_header.xml b/core/res/res/layout/notification_2025_template_header.xml index fc727e1c72f5..2d30d8a8bbb6 100644 --- a/core/res/res/layout/notification_2025_template_header.xml +++ b/core/res/res/layout/notification_2025_template_header.xml @@ -60,7 +60,7 @@ android:layout_height="match_parent" android:layout_alignParentStart="true" android:layout_centerVertical="true" - android:layout_toStartOf="@id/notification_buttons_column" + android:layout_toStartOf="@id/expand_button" android:layout_alignWithParentIfMissing="true" android:clipChildren="false" android:gravity="center_vertical" @@ -81,28 +81,17 @@ android:focusable="false" /> - <LinearLayout - android:id="@+id/notification_buttons_column" + <include layout="@layout/notification_expand_button" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentEnd="true" - android:orientation="vertical" - > - - <include layout="@layout/notification_close_button" - android:layout_width="@dimen/notification_close_button_size" - android:layout_height="@dimen/notification_close_button_size" - android:layout_gravity="end" - android:layout_marginEnd="20dp" - /> - - <include layout="@layout/notification_expand_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentEnd="true" - android:layout_centerVertical="true" - /> + android:layout_centerVertical="true" + android:layout_alignParentEnd="true" /> - </LinearLayout> + <include layout="@layout/notification_close_button" + android:id="@+id/close_button" + android:layout_width="@dimen/notification_close_button_size" + android:layout_height="@dimen/notification_close_button_size" + android:layout_alignParentTop="true" + android:layout_alignParentEnd="true" /> </NotificationHeaderView> diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml index e28b6462bad7..e6295ea06177 100644 --- a/core/res/res/values-watch/config.xml +++ b/core/res/res/values-watch/config.xml @@ -100,4 +100,7 @@ <!-- Whether to enable scaling and fading animation to scrollviews while scrolling. P.S this is a change only intended for wear devices. --> <bool name="config_enableViewGroupScalingFading">true</bool> + + <!-- Allow the gesture to double tap the power button to trigger a target action. --> + <bool name="config_doubleTapPowerGestureEnabled">false</bool> </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 565e28e0cc87..45a5d85a097d 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2165,6 +2165,17 @@ config_enableGeofenceOverlay is false. --> <string name="config_geofenceProviderPackageName" translatable="false">@null</string> + <!-- Whether to enable GNSS assistance overlay which allows GnssAssistanceProvider to be + replaced by an app at run-time. When disabled, only the + config_gnssAssistanceProviderPackageName package will be searched for + GnssAssistanceProvider, otherwise any system package is eligible. Anyone who wants to + disable the overlay mechanism can set it to false. + --> + <bool name="config_enableGnssAssistanceOverlay" translatable="false">true</bool> + <!-- Package name providing GNSS assistance API support. Used only when + config_enableGnssAssistanceOverlay is false. --> + <string name="config_gnssAssistanceProviderPackageName" translatable="false">@null</string> + <!-- Whether to enable Hardware Activity-Recognition overlay which allows Hardware Activity-Recognition to be replaced by an app at run-time. When disabled, only the config_activityRecognitionHardwarePackageName package will be searched for diff --git a/core/res/res/values/config_watch.xml b/core/res/res/values/config_watch.xml index 629a343f1280..bcb1e0941b5a 100644 --- a/core/res/res/values/config_watch.xml +++ b/core/res/res/values/config_watch.xml @@ -15,8 +15,7 @@ --> <resources> - <!-- TODO(b/382103556): use predefined Material3 token --> <!-- For Wear Material3 --> - <dimen name="config_wearMaterial3_buttonCornerRadius">26dp</dimen> - <dimen name="config_wearMaterial3_bottomDialogCornerRadius">18dp</dimen> + <dimen name="config_wearMaterial3_buttonCornerRadius">@dimen/config_shapeCornerRadiusLarge</dimen> + <dimen name="config_wearMaterial3_bottomDialogCornerRadius">@dimen/config_shapeCornerRadiusMedium</dimen> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 73e06f6a2520..8195d38993c8 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2040,6 +2040,7 @@ <java-symbol type="bool" name="config_useGnssHardwareProvider" /> <java-symbol type="bool" name="config_enableGeocoderOverlay" /> <java-symbol type="bool" name="config_enableGeofenceOverlay" /> + <java-symbol type="bool" name="config_enableGnssAssistanceOverlay" /> <java-symbol type="bool" name="config_enableNetworkLocationOverlay" /> <java-symbol type="bool" name="config_sf_limitedAlpha" /> <java-symbol type="bool" name="config_unplugTurnsOnScreen" /> @@ -2223,6 +2224,7 @@ <java-symbol type="string" name="config_gnssLocationProviderPackageName" /> <java-symbol type="string" name="config_geocoderProviderPackageName" /> <java-symbol type="string" name="config_geofenceProviderPackageName" /> + <java-symbol type="string" name="config_gnssAssistanceProviderPackageName" /> <java-symbol type="string" name="config_networkLocationProviderPackageName" /> <java-symbol type="string" name="config_wimaxManagerClassname" /> <java-symbol type="string" name="config_wimaxNativeLibLocation" /> diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index 9effeec23890..ca6ad6fae46e 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -105,6 +105,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.R; import com.android.internal.util.ContrastColorUtil; +import com.android.internal.widget.NotificationProgressModel; import junit.framework.Assert; @@ -2414,7 +2415,7 @@ public class NotificationTest { @Test @EnableFlags(Flags.FLAG_API_RICH_ONGOING) - public void progressStyle_getProgressMax_nooSegments_returnsDefault() { + public void progressStyle_getProgressMax_noSegments_returnsDefault() { final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle(); progressStyle.setProgressSegments(Collections.emptyList()); assertThat(progressStyle.getProgressMax()).isEqualTo(100); @@ -2459,6 +2460,211 @@ public class NotificationTest { @Test @EnableFlags(Flags.FLAG_API_RICH_ONGOING) + public void progressStyle_getProgressMax_onSegmentLimitExceeded_returnsSumOfSegmentLength() { + // GIVEN + final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle(); + // limit is 10 for ProgressStyle + for (int i = 0; i < 30; i++) { + progressStyle + .addProgressSegment(new Notification.ProgressStyle.Segment(10)); + } + + // THEN + assertThat(progressStyle.getProgressMax()).isEqualTo(300); + } + + @Test + @EnableFlags(Flags.FLAG_API_RICH_ONGOING) + public void progressStyle_addProgressSegment_dropsInvalidSegments() { + // GIVEN + final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle(); + // Segments should be a positive integer. + progressStyle + .addProgressSegment(new Notification.ProgressStyle.Segment(0)); + progressStyle + .addProgressSegment(new Notification.ProgressStyle.Segment(-1)); + + // THEN + assertThat(progressStyle.getProgressSegments()).isEmpty(); + } + + @Test + @EnableFlags(Flags.FLAG_API_RICH_ONGOING) + public void progressStyle_setProgressSegment_dropsInvalidSegments() { + // GIVEN + final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle(); + // Segments should be a positive integer. + progressStyle + .setProgressSegments(List.of(new Notification.ProgressStyle.Segment(0), + new Notification.ProgressStyle.Segment(-1))); + + // THEN + assertThat(progressStyle.getProgressSegments()).isEmpty(); + } + + @Test + @EnableFlags(Flags.FLAG_API_RICH_ONGOING) + public void progressStyle_addProgressPoint_dropsNegativePoints() { + // GIVEN + final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle(); + // Points should not be a negative integer. + progressStyle + .addProgressPoint(new Notification.ProgressStyle.Point(-1)) + .addProgressPoint(new Notification.ProgressStyle.Point(-100)); + + // THEN + assertThat(progressStyle.getProgressPoints()).isEmpty(); + } + + @Test + @EnableFlags(Flags.FLAG_API_RICH_ONGOING) + public void progressStyle_setProgressPoint_dropsNegativePoints() { + // GIVEN + final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle(); + // Points should not be a negative integer. + progressStyle + .setProgressPoints(List.of(new Notification.ProgressStyle.Point(-1), + new Notification.ProgressStyle.Point(-100))); + + // THEN + assertThat(progressStyle.getProgressPoints()).isEmpty(); + } + + @Test + @EnableFlags(Flags.FLAG_API_RICH_ONGOING) + public void progressStyle_createProgressModel_ignoresPointsExceedingMax() { + // GIVEN + final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle(); + progressStyle.addProgressSegment(new Notification.ProgressStyle.Segment(100)); + // Points should not larger than progress maximum. + progressStyle + .addProgressPoint(new Notification.ProgressStyle.Point(101)) + .addProgressPoint(new Notification.ProgressStyle.Point(500)); + + // THEN + assertThat(progressStyle.createProgressModel(Color.BLUE, Color.RED).getPoints()).isEmpty(); + } + + @Test + @EnableFlags(Flags.FLAG_API_RICH_ONGOING) + public void progressStyle_createProgressModel_ignoresOverLimitPoints() { + // GIVEN + final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle(); + progressStyle.addProgressSegment(new Notification.ProgressStyle.Segment(100)); + + // maximum 4 points are going to be rendered. + progressStyle + .addProgressPoint(new Notification.ProgressStyle.Point(0)) + .addProgressPoint(new Notification.ProgressStyle.Point(20)) + .addProgressPoint(new Notification.ProgressStyle.Point(150)) + .addProgressPoint(new Notification.ProgressStyle.Point(50)) + .addProgressPoint(new Notification.ProgressStyle.Point(70)) + .addProgressPoint(new Notification.ProgressStyle.Point(80)) + .addProgressPoint(new Notification.ProgressStyle.Point(90)) + .addProgressPoint(new Notification.ProgressStyle.Point(95)) + .addProgressPoint(new Notification.ProgressStyle.Point(100)); + final int backgroundColor = Color.RED; + final int defaultProgressColor = Color.BLUE; + final int expectedProgressColor = Notification.ProgressStyle.sanitizeProgressColor( + /* color = */Notification.COLOR_DEFAULT, + /* bg = */backgroundColor, + /* defaultColor = */defaultProgressColor); + + // THEN + assertThat(progressStyle.createProgressModel(defaultProgressColor, backgroundColor) + .getPoints()).isEqualTo( + List.of(new Notification.ProgressStyle.Point(0) + .setColor(expectedProgressColor), + new Notification.ProgressStyle.Point(20) + .setColor(expectedProgressColor), + new Notification.ProgressStyle.Point(50) + .setColor(expectedProgressColor), + new Notification.ProgressStyle.Point(70) + .setColor(expectedProgressColor) + ) + ); + } + + @Test + @EnableFlags(Flags.FLAG_API_RICH_ONGOING) + public void progressStyle_createProgressModel_mergeSegmentsOnOverflow() { + // GIVEN + final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle(); + + for (int i = 0; i < 15; i++) { + progressStyle + .addProgressSegment(new Notification.ProgressStyle.Segment(10)); + } + + final NotificationProgressModel progressModel = progressStyle.createProgressModel( + Color.BLUE, Color.RED); + + // THEN + assertThat(progressModel.getSegments().size()).isEqualTo(1); + assertThat(progressModel.getProgressMax()).isEqualTo(150); + } + + @Test + @EnableFlags(Flags.FLAG_API_RICH_ONGOING) + public void progressStyle_createProgressModel_useSegmentColorWhenAllMatch() { + // GIVEN + final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle(); + final int segmentColor = Color.YELLOW; + final int defaultProgressColor = Color.BLUE; + final int backgroundColor = Color.RED; + // contrast ensured color for segmentColor. + final int expectedSegmentColor = Notification.ProgressStyle.sanitizeProgressColor( + /* color = */ segmentColor, + /* bg = */ backgroundColor, + /* defaultColor = */ defaultProgressColor); + + for (int i = 0; i < 15; i++) { + progressStyle + .addProgressSegment(new Notification.ProgressStyle.Segment(10) + .setColor(segmentColor)); + } + + final NotificationProgressModel progressModel = progressStyle.createProgressModel( + defaultProgressColor, backgroundColor); + + // THEN + assertThat(progressModel.getSegments()) + .isEqualTo(List.of(new Notification.ProgressStyle.Segment(150) + .setColor(expectedSegmentColor))); + } + + @Test + @EnableFlags(Flags.FLAG_API_RICH_ONGOING) + public void progressStyle_createProgressModel_useDefaultColorWhenAllNotMatch() { + // GIVEN + final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle(); + final int defaultProgressColor = Color.BLUE; + final int backgroundColor = Color.RED; + // contrast ensured color for default progress color. + final int expectedSegmentColor = Notification.ProgressStyle.sanitizeProgressColor( + /* color = */ defaultProgressColor, + /* bg = */ backgroundColor, + /* defaultColor = */ defaultProgressColor); + + for (int i = 0; i < 15; i++) { + progressStyle + .addProgressSegment(new Notification.ProgressStyle.Segment(5) + .setColor(Color.BLUE)) + .addProgressSegment(new Notification.ProgressStyle.Segment(5) + .setColor(Color.CYAN)); + } + + final NotificationProgressModel progressModel = progressStyle.createProgressModel( + defaultProgressColor, backgroundColor); + + // THEN + assertThat(progressModel.getSegments()) + .isEqualTo(List.of(new Notification.ProgressStyle.Segment(150) + .setColor(expectedSegmentColor))); + } + + @Test + @EnableFlags(Flags.FLAG_API_RICH_ONGOING) public void progressStyle_indeterminate_defaultValueFalse() { final Notification.ProgressStyle progressStyle1 = new Notification.ProgressStyle(); diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java index 911b7ce22741..10a85bcbf488 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java +++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java @@ -120,7 +120,8 @@ public class ClientTransactionListenerControllerTest { doReturn(newDisplayInfo).when(mIDisplayManager).getDisplayInfo(123); mDisplayManager.registerDisplayListener(mListener, mHandler, - DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED, + DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE, null /* packageName */); mController.onDisplayChanged(123); diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java index 7a5b3064b3a3..a270848b98a3 100644 --- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java +++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java @@ -73,9 +73,13 @@ public class DisplayManagerGlobalTest { public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private static final long DISPLAY_CHANGE_EVENTS = + DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE; + private static final long ALL_DISPLAY_EVENTS = DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED - | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED + | DISPLAY_CHANGE_EVENTS | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED; @Mock @@ -127,7 +131,7 @@ public class DisplayManagerGlobalTest { final DisplayInfo newDisplayInfo = new DisplayInfo(); newDisplayInfo.rotation++; doReturn(newDisplayInfo).when(mDisplayManager).getDisplayInfo(displayId); - callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); + callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED); waitForHandler(); Mockito.verify(mDisplayListener).onDisplayChanged(eq(displayId)); Mockito.verifyNoMoreInteractions(mDisplayListener); @@ -186,8 +190,8 @@ public class DisplayManagerGlobalTest { mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler, ALL_DISPLAY_EVENTS - & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED, null); - callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); + & ~DISPLAY_CHANGE_EVENTS, null); + callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED); waitForHandler(); Mockito.verifyZeroInteractions(mDisplayListener); @@ -257,8 +261,7 @@ public class DisplayManagerGlobalTest { | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, null /* packageName */); mDisplayManagerGlobal.registerDisplayListener(mDisplayListener2, mHandler, - DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED, - null /* packageName */); + DISPLAY_CHANGE_EVENTS, null /* packageName */); mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(321); waitForHandler(); @@ -304,8 +307,7 @@ public class DisplayManagerGlobalTest { assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED, mDisplayManagerGlobal .mapFlagsToInternalEventFlag(DisplayManager.EVENT_FLAG_DISPLAY_ADDED, 0)); - assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED, - mDisplayManagerGlobal + assertEquals(DISPLAY_CHANGE_EVENTS, mDisplayManagerGlobal .mapFlagsToInternalEventFlag(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, 0)); assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, mDisplayManagerGlobal diff --git a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt index 4a227d8ff1ef..255d09b854bd 100644 --- a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt +++ b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt @@ -86,7 +86,7 @@ class DisplayTopologyTest { verifyDisplay(display1, displayId1, width1, height1, noOfChildren = 1) val display2 = display1.children[0] - verifyDisplay(display1.children[0], displayId2, width2, height2, POSITION_TOP, + verifyDisplay(display2, displayId2, width2, height2, POSITION_TOP, offset = width1 / 2 - width2 / 2, noOfChildren = 1) var display = display2 @@ -99,6 +99,76 @@ class DisplayTopologyTest { } @Test + fun updateDisplay() { + val displayId = 1 + val width = 800f + val height = 600f + + val newWidth = 1000f + val newHeight = 500f + topology.addDisplay(displayId, width, height) + assertThat(topology.updateDisplay(displayId, newWidth, newHeight)).isTrue() + + assertThat(topology.primaryDisplayId).isEqualTo(displayId) + verifyDisplay(topology.root!!, displayId, newWidth, newHeight, noOfChildren = 0) + } + + @Test + fun updateDisplay_notUpdated() { + val displayId = 1 + val width = 800f + val height = 600f + topology.addDisplay(displayId, width, height) + + // Same size + assertThat(topology.updateDisplay(displayId, width, height)).isFalse() + + // Display doesn't exist + assertThat(topology.updateDisplay(/* displayId= */ 100, width, height)).isFalse() + + assertThat(topology.primaryDisplayId).isEqualTo(displayId) + verifyDisplay(topology.root!!, displayId, width, height, noOfChildren = 0) + } + + @Test + fun updateDisplayDoesNotAffectDefaultTopology() { + val width1 = 700f + val height = 600f + topology.addDisplay(/* displayId= */ 1, width1, height) + + val width2 = 800f + val noOfDisplays = 30 + for (i in 2..noOfDisplays) { + topology.addDisplay(/* displayId= */ i, width2, height) + } + + val displaysToUpdate = arrayOf(3, 7, 18) + val newWidth = 1000f + val newHeight = 1500f + for (i in displaysToUpdate) { + assertThat(topology.updateDisplay(/* displayId= */ i, newWidth, newHeight)).isTrue() + } + + assertThat(topology.primaryDisplayId).isEqualTo(1) + + val display1 = topology.root!! + verifyDisplay(display1, id = 1, width1, height, noOfChildren = 1) + + val display2 = display1.children[0] + verifyDisplay(display2, id = 2, width2, height, POSITION_TOP, + offset = width1 / 2 - width2 / 2, noOfChildren = 1) + + var display = display2 + for (i in 3..noOfDisplays) { + display = display.children[0] + // The last display should have no children + verifyDisplay(display, id = i, if (i in displaysToUpdate) newWidth else width2, + if (i in displaysToUpdate) newHeight else height, POSITION_RIGHT, offset = 0f, + noOfChildren = if (i < noOfDisplays) 1 else 0) + } + } + + @Test fun removeDisplays() { val displayId1 = 1 val width1 = 800f @@ -117,7 +187,7 @@ class DisplayTopologyTest { } var removedDisplays = arrayOf(20) - topology.removeDisplay(20) + assertThat(topology.removeDisplay(20)).isTrue() assertThat(topology.primaryDisplayId).isEqualTo(displayId1) @@ -139,11 +209,11 @@ class DisplayTopologyTest { noOfChildren = if (i < noOfDisplays) 1 else 0) } - topology.removeDisplay(22) + assertThat(topology.removeDisplay(22)).isTrue() removedDisplays += 22 - topology.removeDisplay(23) + assertThat(topology.removeDisplay(23)).isTrue() removedDisplays += 23 - topology.removeDisplay(25) + assertThat(topology.removeDisplay(25)).isTrue() removedDisplays += 25 assertThat(topology.primaryDisplayId).isEqualTo(displayId1) @@ -174,7 +244,7 @@ class DisplayTopologyTest { val height = 600f topology.addDisplay(displayId, width, height) - topology.removeDisplay(displayId) + assertThat(topology.removeDisplay(displayId)).isTrue() assertThat(topology.primaryDisplayId).isEqualTo(Display.INVALID_DISPLAY) assertThat(topology.root).isNull() @@ -187,7 +257,7 @@ class DisplayTopologyTest { val height = 600f topology.addDisplay(displayId, width, height) - topology.removeDisplay(3) + assertThat(topology.removeDisplay(3)).isFalse() assertThat(topology.primaryDisplayId).isEqualTo(displayId) verifyDisplay(topology.root!!, displayId, width, height, noOfChildren = 0) @@ -203,7 +273,7 @@ class DisplayTopologyTest { topology = DisplayTopology(/* root= */ null, displayId2) topology.addDisplay(displayId1, width, height) topology.addDisplay(displayId2, width, height) - topology.removeDisplay(displayId2) + assertThat(topology.removeDisplay(displayId2)).isTrue() assertThat(topology.primaryDisplayId).isEqualTo(displayId1) verifyDisplay(topology.root!!, displayId1, width, height, noOfChildren = 0) diff --git a/core/tests/coretests/src/android/os/ProcessTest.java b/core/tests/coretests/src/android/os/ProcessTest.java index 310baa371163..ea39db7b0057 100644 --- a/core/tests/coretests/src/android/os/ProcessTest.java +++ b/core/tests/coretests/src/android/os/ProcessTest.java @@ -18,6 +18,7 @@ package android.os; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import android.platform.test.annotations.IgnoreUnderRavenwood; @@ -26,6 +27,8 @@ import android.platform.test.ravenwood.RavenwoodRule; import org.junit.Rule; import org.junit.Test; +import java.util.Arrays; + @IgnoreUnderRavenwood(blockedBy = Process.class) public class ProcessTest { @Rule @@ -92,4 +95,19 @@ public class ProcessTest { assertTrue(Process.getAdvertisedMem() > 0); assertTrue(Process.getTotalMemory() <= Process.getAdvertisedMem()); } + + @Test + public void testGetSchedAffinity() { + long[] tidMasks = Process.getSchedAffinity(Process.myTid()); + long[] pidMasks = Process.getSchedAffinity(Process.myPid()); + checkAffinityMasks(tidMasks); + checkAffinityMasks(pidMasks); + } + + static void checkAffinityMasks(long[] masks) { + assertNotNull(masks); + assertTrue(masks.length > 0); + assertTrue("At least one of the affinity mask should be non-zero but got " + + Arrays.toString(masks), Arrays.stream(masks).anyMatch(value -> value > 0)); + } } diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index ed9fc1c9e547..18ab52dba8f3 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -25,7 +25,7 @@ import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT; import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE; -import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE; +import static android.view.Surface.FRAME_RATE_COMPATIBILITY_AT_LEAST; import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; @@ -861,10 +861,10 @@ public class ViewRootImplTest { assertEquals(mViewRootImpl.getFrameRateCompatibility(), FRAME_RATE_COMPATIBILITY_FIXED_SOURCE); assertFalse(mViewRootImpl.isFrameRateConflicted()); - mViewRootImpl.votePreferredFrameRate(24, FRAME_RATE_COMPATIBILITY_GTE); + mViewRootImpl.votePreferredFrameRate(24, FRAME_RATE_COMPATIBILITY_AT_LEAST); if (toolkitFrameRateVelocityMappingReadOnly()) { assertEquals(24, mViewRootImpl.getPreferredFrameRate(), 0.1); - assertEquals(FRAME_RATE_COMPATIBILITY_GTE, + assertEquals(FRAME_RATE_COMPATIBILITY_AT_LEAST, mViewRootImpl.getFrameRateCompatibility()); assertFalse(mViewRootImpl.isFrameRateConflicted()); } else { @@ -888,10 +888,10 @@ public class ViewRootImplTest { sInstrumentation.runOnMainSync(() -> { assertFalse(mViewRootImpl.isFrameRateConflicted()); - mViewRootImpl.votePreferredFrameRate(60, FRAME_RATE_COMPATIBILITY_GTE); + mViewRootImpl.votePreferredFrameRate(60, FRAME_RATE_COMPATIBILITY_AT_LEAST); if (toolkitFrameRateVelocityMappingReadOnly()) { assertEquals(60, mViewRootImpl.getPreferredFrameRate(), 0.1); - assertEquals(FRAME_RATE_COMPATIBILITY_GTE, + assertEquals(FRAME_RATE_COMPATIBILITY_AT_LEAST, mViewRootImpl.getFrameRateCompatibility()); } else { assertEquals(FRAME_RATE_CATEGORY_HIGH, @@ -904,7 +904,7 @@ public class ViewRootImplTest { mViewRootImpl.getFrameRateCompatibility()); // Should be false since 60 is a divisor of 120. assertFalse(mViewRootImpl.isFrameRateConflicted()); - mViewRootImpl.votePreferredFrameRate(60, FRAME_RATE_COMPATIBILITY_GTE); + mViewRootImpl.votePreferredFrameRate(60, FRAME_RATE_COMPATIBILITY_AT_LEAST); assertEquals(120, mViewRootImpl.getPreferredFrameRate(), 0.1); // compatibility should be remained the same (FRAME_RATE_COMPATIBILITY_FIXED_SOURCE) // since the frame rate 60 is smaller than 120. diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java index 5a4561d7c6ea..f87b6994900f 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java @@ -266,6 +266,11 @@ public class ContentCaptureSessionTest { } @Override + void internalNotifySessionFlushEvent(int sessionId) { + throw new UnsupportedOperationException("should not have been called"); + } + + @Override void internalNotifyChildSessionStarted(int parentSessionId, int childSessionId, @NonNull ContentCaptureContext clientContext) { throw new UnsupportedOperationException("should not have been called"); diff --git a/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java index fdc00ba65255..610758d378de 100644 --- a/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java +++ b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java @@ -16,8 +16,6 @@ package android.window; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; import static org.junit.Assert.assertEquals; @@ -30,15 +28,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActivityManager.TaskDescription; -import android.content.ComponentName; import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.ColorSpace; -import android.graphics.Point; import android.graphics.Rect; -import android.hardware.HardwareBuffer; -import android.view.Surface; -import android.view.SurfaceControl; import android.view.WindowInsets; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -54,7 +46,7 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class SnapshotDrawerUtilsTest { - private SnapshotDrawerUtils.SnapshotSurface mSnapshotSurface; + private SnapshotDrawerUtils.SystemBarBackgroundPainter mSystemBarBackgroundPainter; private void setupSurface(int width, int height) { setupSurface(width, height, new Rect(), FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, @@ -70,31 +62,14 @@ public class SnapshotDrawerUtilsTest { // taskBounds assertEquals(width, taskBounds.width()); assertEquals(height, taskBounds.height()); - Point taskSize = new Point(taskBounds.width(), taskBounds.height()); - final TaskSnapshot snapshot = createTaskSnapshot(width, height, taskSize, contentInsets); TaskDescription taskDescription = createTaskDescription(Color.WHITE, Color.RED, Color.BLUE); - mSnapshotSurface = new SnapshotDrawerUtils.SnapshotSurface( - new SurfaceControl(), snapshot, "Test"); - mSnapshotSurface.initiateSystemBarPainter(windowFlags, 0, 0, - taskDescription, WindowInsets.Type.defaultVisible()); - } - - private TaskSnapshot createTaskSnapshot(int width, int height, Point taskSize, - Rect contentInsets) { - final HardwareBuffer buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, - 1, HardwareBuffer.USAGE_CPU_READ_RARELY); - return new TaskSnapshot( - System.currentTimeMillis(), - 0 /* captureTime */, - new ComponentName("", ""), buffer, - ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT, - Surface.ROTATION_0, taskSize, contentInsets, new Rect() /* letterboxInsets */, - false, true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, - 0 /* systemUiVisibility */, false /* isTranslucent */, false /* hasImeSurface */, - 0 /* uiMode */); + mSystemBarBackgroundPainter = new SnapshotDrawerUtils.SystemBarBackgroundPainter( + windowFlags, 0 /* windowPrivateFlags */, 0 /* appearance */, + taskDescription, 1f /* scale */, WindowInsets.Type.defaultVisible()); + mSystemBarBackgroundPainter.setInsets(contentInsets); } private static TaskDescription createTaskDescription(int background, @@ -107,134 +82,14 @@ public class SnapshotDrawerUtilsTest { } @Test - public void fillEmptyBackground_fillHorizontally() { - setupSurface(200, 100); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(200); - when(mockCanvas.getHeight()).thenReturn(100); - mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 200)); - verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any()); - } - - @Test - public void fillEmptyBackground_fillVertically() { - setupSurface(100, 200); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(200); - mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 100)); - verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(100.0f), eq(200.0f), any()); - } - - @Test - public void fillEmptyBackground_fillBoth() { - setupSurface(200, 200); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(200); - when(mockCanvas.getHeight()).thenReturn(200); - mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100)); - verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any()); - verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(200.0f), eq(200.0f), any()); - } - - @Test - public void fillEmptyBackground_dontFill_sameSize() { - setupSurface(100, 100); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100)); - verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any()); - } - - @Test - public void fillEmptyBackground_dontFill_bitmapLarger() { - setupSurface(100, 100); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 200)); - verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any()); - } - - @Test - public void testCalculateSnapshotCrop() { - final Rect contentInsets = new Rect(0, 10, 0, 10); - setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(0, 0, 100, 90), - mSnapshotSurface.calculateSnapshotCrop(contentInsets)); - } - - @Test - public void testCalculateSnapshotCrop_taskNotOnTop() { - final Rect contentInsets = new Rect(0, 10, 0, 10); - final Rect bounds = new Rect(0, 50, 100, 150); - setupSurface(100, 100, contentInsets, 0, bounds); - mSnapshotSurface.setFrames(bounds, contentInsets); - assertEquals(new Rect(0, 10, 100, 90), - mSnapshotSurface.calculateSnapshotCrop(contentInsets)); - } - - @Test - public void testCalculateSnapshotCrop_navBarLeft() { - final Rect contentInsets = new Rect(10, 0, 0, 0); - setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(10, 0, 100, 100), - mSnapshotSurface.calculateSnapshotCrop(contentInsets)); - } - - @Test - public void testCalculateSnapshotCrop_navBarRight() { - final Rect contentInsets = new Rect(0, 10, 10, 0); - setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(0, 0, 90, 100), - mSnapshotSurface.calculateSnapshotCrop(contentInsets)); - } - - @Test - public void testCalculateSnapshotCrop_waterfall() { - final Rect contentInsets = new Rect(5, 10, 5, 10); - setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(5, 0, 95, 90), - mSnapshotSurface.calculateSnapshotCrop(contentInsets)); - } - - @Test - public void testCalculateSnapshotFrame() { - setupSurface(100, 100); - final Rect insets = new Rect(0, 10, 0, 10); - mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets); - assertEquals(new Rect(0, 0, 100, 80), - mSnapshotSurface.calculateSnapshotFrame(new Rect(0, 10, 100, 90))); - } - - @Test - public void testCalculateSnapshotFrame_navBarLeft() { - setupSurface(100, 100); - final Rect insets = new Rect(10, 10, 0, 0); - mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets); - assertEquals(new Rect(10, 0, 100, 90), - mSnapshotSurface.calculateSnapshotFrame(new Rect(10, 10, 100, 100))); - } - - @Test - public void testCalculateSnapshotFrame_waterfall() { - setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, new Rect(0, 0, 100, 100)); - final Rect insets = new Rect(0, 10, 0, 10); - mSnapshotSurface.setFrames(new Rect(5, 0, 95, 100), insets); - assertEquals(new Rect(0, 0, 90, 90), - mSnapshotSurface.calculateSnapshotFrame(new Rect(5, 0, 95, 90))); - } - - @Test public void testDrawStatusBarBackground() { setupSurface(100, 100); final Rect insets = new Rect(0, 10, 10, 0); - mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets); + mSystemBarBackgroundPainter.setInsets(insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); - mSnapshotSurface.drawStatusBarBackground(mockCanvas, new Rect(0, 0, 50, 100)); + mSystemBarBackgroundPainter.drawDecors(mockCanvas, new Rect(0, 0, 50, 100)); verify(mockCanvas).drawRect(eq(50.0f), eq(0.0f), eq(90.0f), eq(10.0f), any()); } @@ -242,11 +97,11 @@ public class SnapshotDrawerUtilsTest { public void testDrawStatusBarBackground_nullFrame() { setupSurface(100, 100); final Rect insets = new Rect(0, 10, 10, 0); - mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets); + mSystemBarBackgroundPainter.setInsets(insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); - mSnapshotSurface.drawStatusBarBackground(mockCanvas, null); + mSystemBarBackgroundPainter.drawDecors(mockCanvas, null /* alreadyDrawnFrame */); verify(mockCanvas).drawRect(eq(0.0f), eq(0.0f), eq(90.0f), eq(10.0f), any()); } @@ -254,11 +109,11 @@ public class SnapshotDrawerUtilsTest { public void testDrawStatusBarBackground_nope() { setupSurface(100, 100); final Rect insets = new Rect(0, 10, 10, 0); - mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets); + mSystemBarBackgroundPainter.setInsets(insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); - mSnapshotSurface.drawStatusBarBackground(mockCanvas, new Rect(0, 0, 100, 100)); + mSystemBarBackgroundPainter.drawDecors(mockCanvas, new Rect(0, 0, 100, 100)); verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any()); } @@ -267,11 +122,11 @@ public class SnapshotDrawerUtilsTest { final Rect insets = new Rect(0, 10, 0, 10); setupSurface(100, 100, insets, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, new Rect(0, 0, 100, 100)); - mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets); + mSystemBarBackgroundPainter.setInsets(insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); - mSnapshotSurface.drawNavigationBarBackground(mockCanvas); + mSystemBarBackgroundPainter.drawDecors(mockCanvas, null /* alreadyDrawnFrame */); verify(mockCanvas).drawRect(eq(new Rect(0, 90, 100, 100)), any()); } @@ -280,11 +135,11 @@ public class SnapshotDrawerUtilsTest { final Rect insets = new Rect(10, 10, 0, 0); setupSurface(100, 100, insets, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, new Rect(0, 0, 100, 100)); - mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets); + mSystemBarBackgroundPainter.setInsets(insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); - mSnapshotSurface.drawNavigationBarBackground(mockCanvas); + mSystemBarBackgroundPainter.drawDecors(mockCanvas, null /* alreadyDrawnFrame */); verify(mockCanvas).drawRect(eq(new Rect(0, 0, 10, 100)), any()); } @@ -293,11 +148,11 @@ public class SnapshotDrawerUtilsTest { final Rect insets = new Rect(0, 10, 10, 0); setupSurface(100, 100, insets, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, new Rect(0, 0, 100, 100)); - mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets); + mSystemBarBackgroundPainter.setInsets(insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); - mSnapshotSurface.drawNavigationBarBackground(mockCanvas); + mSystemBarBackgroundPainter.drawDecors(mockCanvas, null /* alreadyDrawnFrame */); verify(mockCanvas).drawRect(eq(new Rect(90, 0, 100, 100)), any()); } } diff --git a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java index bb2fe1bcfc64..84ff40f0dcf0 100644 --- a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java +++ b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java @@ -33,11 +33,15 @@ import android.app.ActivityThread; import android.content.res.Configuration; import android.os.IBinder; import android.os.RemoteException; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.view.IWindowManager; import androidx.test.filters.SmallTest; +import com.android.window.flags.Flags; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -58,6 +62,9 @@ public class WindowTokenClientControllerTest { @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + @Rule + public SetFlagsRule setFlagsRule = new SetFlagsRule(); + @Mock private IWindowManager mWindowManagerService; @Mock @@ -161,6 +168,7 @@ public class WindowTokenClientControllerTest { verify(mWindowManagerService).detachWindowContext(mWindowTokenClient); } + @EnableFlags(Flags.FLAG_TRACK_SYSTEM_UI_CONTEXT_BEFORE_WMS) @Test public void testAttachToDisplayContent_keepTrackWithoutWMS() { // WMS is not initialized diff --git a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java index 00b4f464de50..d1fbc77cbd46 100644 --- a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java +++ b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java @@ -31,6 +31,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.doNothing; @@ -44,20 +45,27 @@ import android.content.ComponentName; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.UserInfo; +import android.hardware.input.IInputManager; +import android.hardware.input.InputManagerGlobal; import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.flag.junit.SetFlagsRule; import android.platform.test.ravenwood.RavenwoodRule; import android.provider.Settings; import android.test.mock.MockContentResolver; +import android.view.InputDevice; import androidx.test.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.util.test.FakeSettingsProvider; +import com.android.internal.widget.flags.Flags; import com.google.android.collect.Lists; @@ -76,6 +84,8 @@ import java.util.concurrent.CompletableFuture; public class LockPatternUtilsTest { @Rule public final RavenwoodRule mRavenwood = new RavenwoodRule(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private ILockSettings mLockSettings; private static final int USER_ID = 1; @@ -395,4 +405,156 @@ public class LockPatternUtilsTest { } }; } + + private InputManagerGlobal.TestSession configureExternalHardwareTest(InputDevice[] devices) + throws RemoteException { + final Context context = new ContextWrapper(InstrumentationRegistry.getTargetContext()); + final ILockSettings ils = mock(ILockSettings.class); + when(ils.getBoolean(anyString(), anyBoolean(), anyInt())).thenThrow(RemoteException.class); + mLockPatternUtils = new LockPatternUtils(context, ils); + + IInputManager inputManagerMock = mock(IInputManager.class); + + int[] deviceIds = new int[devices.length]; + + for (int i = 0; i < devices.length; i++) { + when(inputManagerMock.getInputDevice(i)).thenReturn(devices[i]); + } + + when(inputManagerMock.getInputDeviceIds()).thenReturn(deviceIds); + InputManagerGlobal.TestSession session = + InputManagerGlobal.createTestSession(inputManagerMock); + + return session; + } + + @Test + @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void isPinEnhancedPrivacyEnabled_noDevicesAttached() throws RemoteException { + InputManagerGlobal.TestSession session = configureExternalHardwareTest(new InputDevice[0]); + assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID)); + session.close(); + } + + @Test + @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void isPinEnhancedPrivacyEnabled_noEnabledDeviceAttached() throws RemoteException { + InputDevice.Builder builder = new InputDevice.Builder(); + builder.setEnabled(false); + InputManagerGlobal.TestSession session = + configureExternalHardwareTest(new InputDevice[]{builder.build()}); + assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID)); + session.close(); + } + + @Test + @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void isPinEnhancedPrivacyEnabled_withoutHwKeyboard() throws RemoteException { + InputDevice.Builder builder = new InputDevice.Builder(); + builder.setEnabled(true).setSources(InputDevice.SOURCE_TOUCHSCREEN); + InputManagerGlobal.TestSession session = + configureExternalHardwareTest(new InputDevice[]{builder.build()}); + assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID)); + session.close(); + } + + @Test + @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void isPinEnhancedPrivacyEnabled_withoutFullHwKeyboard() throws RemoteException { + InputDevice.Builder builder = new InputDevice.Builder(); + builder + .setEnabled(true) + .setSources(InputDevice.SOURCE_KEYBOARD) + .setKeyboardType(InputDevice.KEYBOARD_TYPE_NON_ALPHABETIC); + InputManagerGlobal.TestSession session = + configureExternalHardwareTest(new InputDevice[]{builder.build()}); + assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID)); + session.close(); + } + + @Test + @DisableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void isPinEnhancedPrivacyEnabled_withHwKeyboardOldDefault() throws RemoteException { + InputDevice.Builder builder = new InputDevice.Builder(); + builder + .setEnabled(true) + .setSources(InputDevice.SOURCE_KEYBOARD) + .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC); + InputManagerGlobal.TestSession session = + configureExternalHardwareTest(new InputDevice[]{builder.build()}); + assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID)); + session.close(); + } + + @Test + @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void isPinEnhancedPrivacyEnabled_withHwKeyboard() throws RemoteException { + InputDevice.Builder builder = new InputDevice.Builder(); + builder + .setEnabled(true) + .setSources(InputDevice.SOURCE_KEYBOARD) + .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC); + InputManagerGlobal.TestSession session = + configureExternalHardwareTest(new InputDevice[]{builder.build()}); + assertTrue(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID)); + session.close(); + } + + @Test + @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void isVisiblePatternEnabled_noDevices() throws RemoteException { + InputManagerGlobal.TestSession session = configureExternalHardwareTest(new InputDevice[0]); + assertTrue(mLockPatternUtils.isVisiblePatternEnabled(USER_ID)); + session.close(); + } + + @Test + @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void isVisiblePatternEnabled_noEnabledDevices() throws RemoteException { + InputDevice.Builder builder = new InputDevice.Builder(); + builder.setEnabled(false); + InputManagerGlobal.TestSession session = + configureExternalHardwareTest(new InputDevice[]{builder.build()}); + assertTrue(mLockPatternUtils.isVisiblePatternEnabled(USER_ID)); + session.close(); + } + + @Test + @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void isVisiblePatternEnabled_noPointingDevices() throws RemoteException { + InputDevice.Builder builder = new InputDevice.Builder(); + builder + .setEnabled(true) + .setSources(InputDevice.SOURCE_TOUCHSCREEN); + InputManagerGlobal.TestSession session = + configureExternalHardwareTest(new InputDevice[]{builder.build()}); + assertTrue(mLockPatternUtils.isVisiblePatternEnabled(USER_ID)); + session.close(); + } + + @Test + @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void isVisiblePatternEnabled_externalPointingDevice() throws RemoteException { + InputDevice.Builder builder = new InputDevice.Builder(); + builder + .setEnabled(true) + .setSources(InputDevice.SOURCE_CLASS_POINTER); + InputManagerGlobal.TestSession session = + configureExternalHardwareTest(new InputDevice[]{builder.build()}); + assertFalse(mLockPatternUtils.isVisiblePatternEnabled(USER_ID)); + session.close(); + } + + @Test + @DisableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void isVisiblePatternEnabled_externalPointingDeviceOldDefault() throws RemoteException { + InputDevice.Builder builder = new InputDevice.Builder(); + builder + .setEnabled(true) + .setSources(InputDevice.SOURCE_CLASS_POINTER); + InputManagerGlobal.TestSession session = + configureExternalHardwareTest(new InputDevice[]{builder.build()}); + assertTrue(mLockPatternUtils.isVisiblePatternEnabled(USER_ID)); + session.close(); + } } diff --git a/libs/WindowManager/Shell/multivalentTests/Android.bp b/libs/WindowManager/Shell/multivalentTests/Android.bp index 41d1b5c15369..eecf199a3ec2 100644 --- a/libs/WindowManager/Shell/multivalentTests/Android.bp +++ b/libs/WindowManager/Shell/multivalentTests/Android.bp @@ -55,6 +55,7 @@ android_robolectric_test { "truth", "flag-junit-base", "flag-junit", + "testables", ], auto_gen_config: true, } @@ -77,6 +78,7 @@ android_test { "truth", "platform-test-annotations", "platform-test-rules", + "testables", ], libs: [ "android.test.base.stubs.system", diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt index 0d8f80935f5a..3e01256fd67c 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt @@ -16,6 +16,8 @@ package com.android.wm.shell.bubbles.bar +import android.animation.AnimatorTestRule +import android.app.ActivityManager import android.content.Context import android.graphics.Insets import android.graphics.Rect @@ -23,7 +25,6 @@ import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.widget.FrameLayout -import androidx.core.animation.AnimatorTestRule import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -36,27 +37,34 @@ import com.android.wm.shell.bubbles.BubbleExpandedViewManager import com.android.wm.shell.bubbles.BubbleLogger import com.android.wm.shell.bubbles.BubbleOverflow import com.android.wm.shell.bubbles.BubblePositioner +import com.android.wm.shell.bubbles.BubbleTaskView import com.android.wm.shell.bubbles.DeviceConfig import com.android.wm.shell.bubbles.FakeBubbleExpandedViewManager import com.android.wm.shell.bubbles.FakeBubbleFactory -import com.android.wm.shell.bubbles.FakeBubbleTaskViewFactory +import com.android.wm.shell.taskview.TaskView +import com.android.wm.shell.taskview.TaskViewTaskController import com.google.common.truth.Truth.assertThat import java.util.concurrent.Semaphore import java.util.concurrent.TimeUnit import org.junit.After import org.junit.Before -import org.junit.ClassRule +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.clearInvocations +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever /** Tests for [BubbleBarAnimationHelper] */ @SmallTest @RunWith(AndroidJUnit4::class) class BubbleBarAnimationHelperTest { - companion object { - @JvmField @ClassRule val animatorTestRule: AnimatorTestRule = AnimatorTestRule() + @get:Rule val animatorTestRule: AnimatorTestRule = AnimatorTestRule(this) + companion object { const val SCREEN_WIDTH = 2000 const val SCREEN_HEIGHT = 1000 } @@ -148,6 +156,26 @@ class BubbleBarAnimationHelperTest { } @Test + fun animateSwitch_bubbleToBubble_updateTaskBounds() { + val fromBubble = createBubble("from").initialize(container) + val toBubbleTaskController = mock<TaskViewTaskController>() + val toBubble = createBubble("to", toBubbleTaskController).initialize(container) + + getInstrumentation().runOnMainSync { + animationHelper.animateSwitch(fromBubble, toBubble) {} + // Start the animation, but don't finish + animatorTestRule.advanceTimeBy(100) + } + getInstrumentation().waitForIdleSync() + // Clear invocations to ensure that bounds update happens after animation ends + clearInvocations(toBubbleTaskController) + getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(900) } + getInstrumentation().waitForIdleSync() + + verify(toBubbleTaskController).setWindowBounds(any()) + } + + @Test fun animateSwitch_bubbleToOverflow_oldHiddenNewShown() { val fromBubble = createBubble(key = "from").initialize(container) val overflow = createOverflow().initialize(container) @@ -193,13 +221,43 @@ class BubbleBarAnimationHelperTest { assertThat(toBubble.bubbleBarExpandedView?.isSurfaceZOrderedOnTop).isFalse() } - private fun createBubble(key: String): Bubble { + @Test + fun animateToRestPosition_updateTaskBounds() { + val taskController = mock<TaskViewTaskController>() + val bubble = createBubble("key", taskController).initialize(container) + + getInstrumentation().runOnMainSync { + animationHelper.animateExpansion(bubble) {} + animatorTestRule.advanceTimeBy(1000) + } + getInstrumentation().waitForIdleSync() + getInstrumentation().runOnMainSync { + animationHelper.animateToRestPosition() + animatorTestRule.advanceTimeBy(100) + } + // Clear invocations to ensure that bounds update happens after animation ends + clearInvocations(taskController) + getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(900) } + getInstrumentation().waitForIdleSync() + + verify(taskController).setWindowBounds(any()) + } + + private fun createBubble( + key: String, + taskViewTaskController: TaskViewTaskController = mock<TaskViewTaskController>(), + ): Bubble { + val taskView = TaskView(context, taskViewTaskController) + val taskInfo = mock<ActivityManager.RunningTaskInfo>() + whenever(taskViewTaskController.taskInfo).thenReturn(taskInfo) + val bubbleTaskView = BubbleTaskView(taskView, mainExecutor) + val bubbleBarExpandedView = FakeBubbleFactory.createExpandedView( context, bubblePositioner, expandedViewManager, - FakeBubbleTaskViewFactory(context, mainExecutor).create(), + bubbleTaskView, mainExecutor, bgExecutor, bubbleLogger, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java index 3e8a9b64dac6..3188e5b9c6d2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java @@ -463,6 +463,7 @@ public class BubbleBarAnimationHelper { super.onAnimationEnd(animation); bbev.resetPivot(); bbev.setDragging(false); + updateExpandedView(bbev); } }); startNewAnimator(animatorSet); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java index c74bf53268f9..9ebb7f5aa270 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java @@ -643,7 +643,9 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged t.setPosition(animatingLeash, x, endY); t.setAlpha(animatingLeash, 1.f); } - dispatchEndPositioning(mDisplayId, mCancelled, t); + if (!android.view.inputmethod.Flags.refactorInsetsController()) { + dispatchEndPositioning(mDisplayId, mCancelled, t); + } if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) { ImeTracker.forLogging().onProgress(mStatsToken, ImeTracker.PHASE_WM_ANIMATION_RUNNING); @@ -659,6 +661,14 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged ImeTracker.forLogging().onCancelled(mStatsToken, ImeTracker.PHASE_WM_ANIMATION_RUNNING); } + if (android.view.inputmethod.Flags.refactorInsetsController()) { + // In split screen, we also set {@link + // WindowContainer#mExcludeInsetsTypes} but this should only happen after + // the IME client visibility was set. Otherwise the insets will we + // dispatched too early, and we get a flicker. Thus, only dispatching it + // after reporting that the IME is hidden to system server. + dispatchEndPositioning(mDisplayId, mCancelled, t); + } if (DEBUG_IME_VISIBILITY) { EventLog.writeEvent(IMF_IME_REMOTE_ANIM_END, mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index 21c44c9b92ee..4bcec702281d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -571,9 +571,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange // For flexible split, expand app offscreen as well if (mDividerSnapAlgorithm.areOffscreenRatiosSupported()) { if (position <= mDividerSnapAlgorithm.getMiddleTarget().position) { - bounds1.top = bounds1.bottom - bounds2.width(); + bounds1.top = bounds1.bottom - bounds2.height(); } else { - bounds2.bottom = bounds2.top + bounds1.width(); + bounds2.bottom = bounds2.top + bounds1.height(); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt index dfa2d9b6bb63..9a60cfeed7c1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt @@ -38,6 +38,7 @@ import androidx.core.util.putAll import com.android.internal.protolog.ProtoLog import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON @@ -236,6 +237,7 @@ class DesktopModeLoggerTransitionObserver( ) { // Sessions is finishing, log task updates followed by an exit event identifyAndLogTaskUpdates( + transitionInfo, preTransitionVisibleFreeformTasks, postTransitionVisibleFreeformTasks, ) @@ -252,12 +254,14 @@ class DesktopModeLoggerTransitionObserver( desktopModeEventLogger.logSessionEnter(getEnterReason(transitionInfo)) identifyAndLogTaskUpdates( + transitionInfo, preTransitionVisibleFreeformTasks, postTransitionVisibleFreeformTasks, ) } else if (isSessionActive) { // Session is neither starting, nor finishing, log task updates if there are any identifyAndLogTaskUpdates( + transitionInfo, preTransitionVisibleFreeformTasks, postTransitionVisibleFreeformTasks, ) @@ -270,6 +274,7 @@ class DesktopModeLoggerTransitionObserver( /** Compare the old and new state of taskInfos and identify and log the changes */ private fun identifyAndLogTaskUpdates( + transitionInfo: TransitionInfo, preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, ) { @@ -304,9 +309,19 @@ class DesktopModeLoggerTransitionObserver( // find old tasks that were removed preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo -> if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) { - desktopModeEventLogger.logTaskRemoved( - buildTaskUpdateForTask(taskInfo, postTransitionVisibleFreeformTasks.size()) - ) + val minimizeReason = + if (transitionInfo.type == Transitions.TRANSIT_MINIMIZE) { + MinimizeReason.MINIMIZE_BUTTON + } else { + null + } + val taskUpdate = + buildTaskUpdateForTask( + taskInfo, + postTransitionVisibleFreeformTasks.size(), + minimizeReason, + ) + desktopModeEventLogger.logTaskRemoved(taskUpdate) Trace.setCounter( Trace.TRACE_TAG_WINDOW_MANAGER, VISIBLE_TASKS_COUNTER_NAME, @@ -320,7 +335,11 @@ class DesktopModeLoggerTransitionObserver( } } - private fun buildTaskUpdateForTask(taskInfo: TaskInfo, visibleTasks: Int): TaskUpdate { + private fun buildTaskUpdateForTask( + taskInfo: TaskInfo, + visibleTasks: Int, + minimizeReason: MinimizeReason? = null, + ): TaskUpdate { val screenBounds = taskInfo.configuration.windowConfiguration.bounds val positionInParent = taskInfo.positionInParent return TaskUpdate( @@ -331,6 +350,7 @@ class DesktopModeLoggerTransitionObserver( taskX = positionInParent.x, taskY = positionInParent.y, visibleTaskCount = visibleTasks, + minimizeReason = minimizeReason, ) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt index 606a729305b4..90191345147c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt @@ -82,7 +82,7 @@ fun calculateInitialBounds( // For portrait resizeable activities, respect apps fullscreen width but // apply ideal size height. Size( - taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth, + taskInfo.appCompatTaskInfo.topActivityAppBounds.width(), idealSize.height, ) } else { @@ -104,7 +104,7 @@ fun calculateInitialBounds( // apply custom app width. Size( customPortraitWidthForLandscapeApp, - taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight, + taskInfo.appCompatTaskInfo.topActivityAppBounds.height(), ) } else { // For portrait resizeable activities, simply apply ideal size. @@ -196,13 +196,8 @@ fun maximizeSizeGivenAspectRatio( /** Calculates the aspect ratio of an activity from its fullscreen bounds. */ fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float { - val appLetterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth - val appLetterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight - if (taskInfo.appCompatTaskInfo.isTopActivityLetterboxed || !taskInfo.canChangeAspectRatio) { - return maxOf(appLetterboxWidth, appLetterboxHeight) / - minOf(appLetterboxWidth, appLetterboxHeight).toFloat() - } - val appBounds = taskInfo.configuration.windowConfiguration.appBounds ?: return 1f + if (taskInfo.appCompatTaskInfo.topActivityAppBounds.isEmpty) return 1f + val appBounds = taskInfo.appCompatTaskInfo.topActivityAppBounds return maxOf(appBounds.height(), appBounds.width()) / minOf(appBounds.height(), appBounds.width()).toFloat() } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java index a611fe1db2ce..c4ff87d175a7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java @@ -74,7 +74,7 @@ public class DragSession { mInitialDragData = data; mInitialDragFlags = dragFlags; displayLayout = dispLayout; - hideDragSourceTaskId = data.getDescription().getExtras() != null + hideDragSourceTaskId = data != null && data.getDescription().getExtras() != null ? data.getDescription().getExtras().getInt(EXTRA_HIDE_DRAG_SOURCE_TASK_ID, -1) : -1; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java index 27472493a8bc..0519a5e055be 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java @@ -89,8 +89,6 @@ public class TaskSnapshotWindow { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, "create taskSnapshot surface for task: %d", taskId); - final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState; - final WindowManager.LayoutParams layoutParams = SnapshotDrawerUtils.createLayoutParameters( info, TITLE_FORMAT + taskId, TYPE_APPLICATION_STARTING, snapshot.getHardwareBuffer().getFormat(), appToken); @@ -152,8 +150,8 @@ public class TaskSnapshotWindow { return null; } - SnapshotDrawerUtils.drawSnapshotOnSurface(info, layoutParams, surfaceControl, snapshot, - info.taskBounds, topWindowInsetsState, true /* releaseAfterDraw */); + SnapshotDrawerUtils.drawSnapshotOnSurface(layoutParams, surfaceControl, snapshot, + info.taskBounds, true /* releaseAfterDraw */); snapshotSurface.mHasDrawn = true; snapshotSurface.reportDrawn(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java index 2a22d4dd0cb5..34d1011bac0e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java @@ -26,7 +26,6 @@ import android.content.Context; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.view.Display; -import android.view.InsetsState; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; import android.view.WindowManager; @@ -77,12 +76,11 @@ class WindowlessSnapshotWindowCreator { final SurfaceControlViewHost mViewHost = new SurfaceControlViewHost( mContext, display, wlw, "WindowlessSnapshotWindowCreator"); final Rect windowBounds = runningTaskInfo.configuration.windowConfiguration.getBounds(); - final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState; final FrameLayout rootLayout = new FrameLayout( mSplashscreenContentDrawer.createViewContextWrapper(mContext)); mViewHost.setView(rootLayout, lp); - SnapshotDrawerUtils.drawSnapshotOnSurface(info, lp, wlw.mChildSurface, snapshot, - windowBounds, topWindowInsetsState, false /* releaseAfterDraw */); + SnapshotDrawerUtils.drawSnapshotOnSurface(lp, wlw.mChildSurface, snapshot, + windowBounds, false /* releaseAfterDraw */); final ActivityManager.TaskDescription taskDescription = SnapshotDrawerUtils.getOrCreateTaskDescription(runningTaskInfo); diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt index 9972247df46d..ab1ac1a0efa3 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt @@ -44,6 +44,7 @@ import android.tools.flicker.assertors.assertions.AppWindowOnTopAtStart import android.tools.flicker.assertors.assertions.AppWindowRemainInsideDisplayBounds import android.tools.flicker.assertors.assertions.AppWindowReturnsToStartBoundsAndPosition import android.tools.flicker.assertors.assertions.LauncherWindowReplacesAppAsTopWindow +import android.tools.flicker.assertors.assertions.VisibleLayersShownMoreThanOneConsecutiveEntry import android.tools.flicker.config.AssertionTemplates import android.tools.flicker.config.FlickerConfigEntry import android.tools.flicker.config.ScenarioId @@ -463,7 +464,9 @@ class DesktopModeFlickerScenarios { } ), assertions = - AssertionTemplates.COMMON_ASSERTIONS + + AssertionTemplates.COMMON_ASSERTIONS.toMutableMap().also { + it.remove(VisibleLayersShownMoreThanOneConsecutiveEntry()) + } + listOf( AppWindowOnTopAtStart(DESKTOP_MODE_APP), AppWindowBecomesInvisible(DESKTOP_MODE_APP), @@ -489,7 +492,9 @@ class DesktopModeFlickerScenarios { } ), assertions = - AssertionTemplates.COMMON_ASSERTIONS + + AssertionTemplates.COMMON_ASSERTIONS.toMutableMap().also { + it.remove(VisibleLayersShownMoreThanOneConsecutiveEntry()) + } + listOf( AppWindowOnTopAtStart(DESKTOP_MODE_APP), AppWindowBecomesInvisible(DESKTOP_MODE_APP), diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsWithKeyboard.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsWithKeyboard.kt new file mode 100644 index 000000000000..56f1dcbf838c --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsWithKeyboard.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker + +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_APP +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_LAST_APP +import com.android.wm.shell.scenarios.MinimizeAppWindows +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Minimize app windows by pressing META + -. + * + * Assert that the app windows gets hidden. + */ +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class MinimizeAppsWithKeyboard : MinimizeAppWindows(usingKeyboard = true) { + @ExpectedScenarios(["MINIMIZE_APP", "MINIMIZE_LAST_APP"]) + @Test + override fun minimizeAllAppWindows() = super.minimizeAllAppWindows() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig() + .use(FlickerServiceConfig.DEFAULT) + .use(MINIMIZE_APP) + .use(MINIMIZE_LAST_APP) + } +} diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt index 971637b62604..835559cd0936 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt @@ -43,7 +43,10 @@ import org.junit.Test */ @Ignore("Test Base Class") abstract class MinimizeAppWindows -constructor(private val rotation: Rotation = Rotation.ROTATION_0) { +constructor( + private val rotation: Rotation = Rotation.ROTATION_0, + private val usingKeyboard: Boolean = false +) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() private val tapl = LauncherInstrumentation() private val wmHelper = WindowManagerStateHelper(instrumentation) @@ -68,9 +71,9 @@ constructor(private val rotation: Rotation = Rotation.ROTATION_0) { @Test open fun minimizeAllAppWindows() { - testApp3.minimizeDesktopApp(wmHelper, device) - testApp2.minimizeDesktopApp(wmHelper, device) - testApp1.minimizeDesktopApp(wmHelper, device) + testApp3.minimizeDesktopApp(wmHelper, device, usingKeyboard = usingKeyboard) + testApp2.minimizeDesktopApp(wmHelper, device, usingKeyboard = usingKeyboard) + testApp1.minimizeDesktopApp(wmHelper, device, usingKeyboard = usingKeyboard) } @After diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt index abd707817621..c0ff2f0652b3 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt @@ -45,6 +45,7 @@ import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_M import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_UNMINIMIZE_REASON import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason import com.google.common.truth.Truth.assertThat +import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test @@ -77,6 +78,12 @@ class DesktopModeEventLoggerTest : ShellTestCase() { doReturn(DISPLAY_HEIGHT).whenever(displayLayout).height() } + @After + fun tearDown() { + clearInvocations(staticMockMarker(FrameworkStatsLog::class.java)) + clearInvocations(staticMockMarker(EventLogTags::class.java)) + } + @Test fun logSessionEnter_logsEnterReasonWithNewSessionId() { desktopModeEventLogger.logSessionEnter(EnterReason.KEYBOARD_SHORTCUT_ENTER) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt index 43684fb92b64..0154ff31c989 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt @@ -46,6 +46,7 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON @@ -566,7 +567,10 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { assertFalse(transitionObserver.isSessionActive) verify(desktopModeEventLogger, times(1)).logSessionExit(eq(ExitReason.TASK_MINIMIZED)) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(DEFAULT_TASK_UPDATE)) + verify(desktopModeEventLogger, times(1)) + .logTaskRemoved( + eq(DEFAULT_TASK_UPDATE.copy(minimizeReason = MinimizeReason.MINIMIZE_BUTTON)) + ) verifyZeroInteractions(desktopModeEventLogger) } 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 4f37180baa37..e1c2153014fa 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 @@ -4160,8 +4160,7 @@ class DesktopTasksControllerTest : ShellTestCase() { screenOrientation = SCREEN_ORIENTATION_LANDSCAPE configuration.windowConfiguration.appBounds = bounds } - appCompatTaskInfo.topActivityLetterboxAppWidth = bounds.width() - appCompatTaskInfo.topActivityLetterboxAppHeight = bounds.height() + appCompatTaskInfo.topActivityAppBounds.set(0, 0, bounds.width(), bounds.height()) isResizeable = false } @@ -4879,15 +4878,19 @@ class DesktopTasksControllerTest : ShellTestCase() { appCompatTaskInfo.isSystemFullscreenOverrideEnabled = enableSystemFullscreenOverride if (deviceOrientation == ORIENTATION_LANDSCAPE) { - configuration.windowConfiguration.appBounds = - Rect(0, 0, DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT) - appCompatTaskInfo.topActivityLetterboxAppWidth = DISPLAY_DIMENSION_LONG - appCompatTaskInfo.topActivityLetterboxAppHeight = DISPLAY_DIMENSION_SHORT + appCompatTaskInfo.topActivityAppBounds.set( + 0, + 0, + DISPLAY_DIMENSION_LONG, + DISPLAY_DIMENSION_SHORT, + ) } else { - configuration.windowConfiguration.appBounds = - Rect(0, 0, DISPLAY_DIMENSION_SHORT, DISPLAY_DIMENSION_LONG) - appCompatTaskInfo.topActivityLetterboxAppWidth = DISPLAY_DIMENSION_SHORT - appCompatTaskInfo.topActivityLetterboxAppHeight = DISPLAY_DIMENSION_LONG + appCompatTaskInfo.topActivityAppBounds.set( + 0, + 0, + DISPLAY_DIMENSION_SHORT, + DISPLAY_DIMENSION_LONG, + ) } if (shouldLetterbox) { @@ -4897,17 +4900,15 @@ class DesktopTasksControllerTest : ShellTestCase() { screenOrientation == SCREEN_ORIENTATION_PORTRAIT ) { // Letterbox to portrait size - appCompatTaskInfo.setTopActivityLetterboxed(true) - appCompatTaskInfo.topActivityLetterboxAppWidth = 1200 - appCompatTaskInfo.topActivityLetterboxAppHeight = 1600 + appCompatTaskInfo.isTopActivityLetterboxed = true + appCompatTaskInfo.topActivityAppBounds.set(0, 0, 1200, 1600) } else if ( deviceOrientation == ORIENTATION_PORTRAIT && screenOrientation == SCREEN_ORIENTATION_LANDSCAPE ) { // Letterbox to landscape size - appCompatTaskInfo.setTopActivityLetterboxed(true) - appCompatTaskInfo.topActivityLetterboxAppWidth = 1600 - appCompatTaskInfo.topActivityLetterboxAppHeight = 1200 + appCompatTaskInfo.isTopActivityLetterboxed = true + appCompatTaskInfo.topActivityAppBounds.set(0, 0, 1600, 1200) } } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragSessionTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragSessionTest.kt index 3d59342f62d8..8ccca07142aa 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragSessionTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragSessionTest.kt @@ -59,6 +59,13 @@ class DragSessionTest : ShellTestCase() { } @Test + fun testNullClipData() { + // Start a new drag session with null data + val session = DragSession(activityTaskManager, displayLayout, null, 0) + assertThat(session.hideDragSourceTaskId).isEqualTo(-1) + } + + @Test fun testGetRunningTask() { // Set up running tasks val runningTasks = listOf( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java index ce482cdd9944..4b01d841b824 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java @@ -55,7 +55,6 @@ import android.os.IBinder; import android.os.Looper; import android.os.UserHandle; import android.testing.TestableContext; -import android.view.InsetsState; import android.view.Surface; import android.view.WindowManager; import android.view.WindowMetrics; @@ -338,9 +337,7 @@ public class StartingSurfaceDrawerTests extends ShellTestCase { windowInfo.appToken = appToken; windowInfo.targetActivityInfo = info; windowInfo.taskInfo = taskInfo; - windowInfo.topOpaqueWindowInsetsState = new InsetsState(); windowInfo.mainWindowLayoutParams = new WindowManager.LayoutParams(); - windowInfo.topOpaqueWindowLayoutParams = new WindowManager.LayoutParams(); return windowInfo; } diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index 1bc15d72bacc..cc4a29b31996 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -199,6 +199,7 @@ cc_test { // This is to suppress warnings/errors from gtest "-Wno-unnamed-type-template-args", ], + require_root: true, srcs: [ // Helpers/infra for testing. "tests/CommonHelpers.cpp", diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp index e6182454ad8a..5955915c9fcd 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -1420,18 +1420,20 @@ void AssetManager::mergeInfoLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo Mutex AssetManager::SharedZip::gLock; DefaultKeyedVector<String8, wp<AssetManager::SharedZip> > AssetManager::SharedZip::gOpen; -AssetManager::SharedZip::SharedZip(const String8& path, time_t modWhen) - : mPath(path), mZipFile(NULL), mModWhen(modWhen), - mResourceTableAsset(NULL), mResourceTable(NULL) -{ - if (kIsDebug) { - ALOGI("Creating SharedZip %p %s\n", this, mPath.c_str()); - } - ALOGV("+++ opening zip '%s'\n", mPath.c_str()); - mZipFile = ZipFileRO::open(mPath.c_str()); - if (mZipFile == NULL) { - ALOGD("failed to open Zip archive '%s'\n", mPath.c_str()); - } +AssetManager::SharedZip::SharedZip(const String8& path, ModDate modWhen) + : mPath(path), + mZipFile(NULL), + mModWhen(modWhen), + mResourceTableAsset(NULL), + mResourceTable(NULL) { + if (kIsDebug) { + ALOGI("Creating SharedZip %p %s\n", this, mPath.c_str()); + } + ALOGV("+++ opening zip '%s'\n", mPath.c_str()); + mZipFile = ZipFileRO::open(mPath.c_str()); + if (mZipFile == NULL) { + ALOGD("failed to open Zip archive '%s'\n", mPath.c_str()); + } } AssetManager::SharedZip::SharedZip(int fd, const String8& path) @@ -1453,7 +1455,7 @@ sp<AssetManager::SharedZip> AssetManager::SharedZip::get(const String8& path, bool createIfNotPresent) { AutoMutex _l(gLock); - time_t modWhen = getFileModDate(path.c_str()); + auto modWhen = getFileModDate(path.c_str()); sp<SharedZip> zip = gOpen.valueFor(path).promote(); if (zip != NULL && zip->mModWhen == modWhen) { return zip; @@ -1520,8 +1522,8 @@ ResTable* AssetManager::SharedZip::setResourceTable(ResTable* res) bool AssetManager::SharedZip::isUpToDate() { - time_t modWhen = getFileModDate(mPath.c_str()); - return mModWhen == modWhen; + auto modWhen = getFileModDate(mPath.c_str()); + return mModWhen == modWhen; } void AssetManager::SharedZip::addOverlay(const asset_path& ap) diff --git a/libs/androidfw/TypeWrappers.cpp b/libs/androidfw/TypeWrappers.cpp index 70d14a11830e..970463492c1a 100644 --- a/libs/androidfw/TypeWrappers.cpp +++ b/libs/androidfw/TypeWrappers.cpp @@ -16,8 +16,6 @@ #include <androidfw/TypeWrappers.h> -#include <algorithm> - namespace android { TypeVariant::TypeVariant(const ResTable_type* data) : data(data), mLength(dtohl(data->entryCount)) { @@ -31,30 +29,44 @@ TypeVariant::TypeVariant(const ResTable_type* data) : data(data), mLength(dtohl( ALOGE("Type's entry indices extend beyond its boundaries"); mLength = 0; } else { - mLength = ResTable_sparseTypeEntry{entryIndices[entryCount - 1]}.idx + 1; + mLength = dtohs(ResTable_sparseTypeEntry{entryIndices[entryCount - 1]}.idx) + 1; } } } TypeVariant::iterator& TypeVariant::iterator::operator++() { - mIndex++; + ++mIndex; if (mIndex > mTypeVariant->mLength) { mIndex = mTypeVariant->mLength; } - return *this; -} -static bool keyCompare(uint32_t entry, uint16_t index) { - return dtohs(ResTable_sparseTypeEntry{entry}.idx) < index; + const ResTable_type* type = mTypeVariant->data; + if ((type->flags & ResTable_type::FLAG_SPARSE) == 0) { + return *this; + } + + // Need to adjust |mSparseIndex| as well if we've passed its current element. + const uint32_t entryCount = dtohl(type->entryCount); + const auto entryIndices = reinterpret_cast<const uint32_t*>( + reinterpret_cast<uintptr_t>(type) + dtohs(type->header.headerSize)); + if (mSparseIndex >= entryCount) { + return *this; // done + } + const auto element = (const ResTable_sparseTypeEntry*)(entryIndices + mSparseIndex); + if (mIndex > dtohs(element->idx)) { + ++mSparseIndex; + } + + return *this; } const ResTable_entry* TypeVariant::iterator::operator*() const { - const ResTable_type* type = mTypeVariant->data; if (mIndex >= mTypeVariant->mLength) { - return NULL; + return nullptr; } - const uint32_t entryCount = dtohl(mTypeVariant->data->entryCount); + const ResTable_type* type = mTypeVariant->data; + const uint32_t entryCount = dtohl(type->entryCount); const uintptr_t containerEnd = reinterpret_cast<uintptr_t>(type) + dtohl(type->header.size); const uint32_t* const entryIndices = reinterpret_cast<const uint32_t*>( @@ -63,18 +75,19 @@ const ResTable_entry* TypeVariant::iterator::operator*() const { sizeof(uint16_t) : sizeof(uint32_t); if (reinterpret_cast<uintptr_t>(entryIndices) + (indexSize * entryCount) > containerEnd) { ALOGE("Type's entry indices extend beyond its boundaries"); - return NULL; + return nullptr; } uint32_t entryOffset; if (type->flags & ResTable_type::FLAG_SPARSE) { - auto iter = std::lower_bound(entryIndices, entryIndices + entryCount, mIndex, keyCompare); - if (iter == entryIndices + entryCount - || dtohs(ResTable_sparseTypeEntry{*iter}.idx) != mIndex) { - return NULL; + if (mSparseIndex >= entryCount) { + return nullptr; } - - entryOffset = static_cast<uint32_t>(dtohs(ResTable_sparseTypeEntry{*iter}.offset)) * 4u; + const auto element = (const ResTable_sparseTypeEntry*)(entryIndices + mSparseIndex); + if (dtohs(element->idx) != mIndex) { + return nullptr; + } + entryOffset = static_cast<uint32_t>(dtohs(element->offset)) * 4u; } else if (type->flags & ResTable_type::FLAG_OFFSET16) { auto entryIndices16 = reinterpret_cast<const uint16_t*>(entryIndices); entryOffset = offset_from16(entryIndices16[mIndex]); @@ -83,25 +96,25 @@ const ResTable_entry* TypeVariant::iterator::operator*() const { } if (entryOffset == ResTable_type::NO_ENTRY) { - return NULL; + return nullptr; } if ((entryOffset & 0x3) != 0) { ALOGE("Index %u points to entry with unaligned offset 0x%08x", mIndex, entryOffset); - return NULL; + return nullptr; } const ResTable_entry* entry = reinterpret_cast<const ResTable_entry*>( reinterpret_cast<uintptr_t>(type) + dtohl(type->entriesStart) + entryOffset); if (reinterpret_cast<uintptr_t>(entry) > containerEnd - sizeof(*entry)) { ALOGE("Entry offset at index %u points outside the Type's boundaries", mIndex); - return NULL; + return nullptr; } else if (reinterpret_cast<uintptr_t>(entry) + entry->size() > containerEnd) { ALOGE("Entry at index %u extends beyond Type's boundaries", mIndex); - return NULL; + return nullptr; } else if (entry->size() < sizeof(*entry)) { ALOGE("Entry at index %u is too small (%zu)", mIndex, entry->size()); - return NULL; + return nullptr; } return entry; } diff --git a/libs/androidfw/include/androidfw/AssetManager.h b/libs/androidfw/include/androidfw/AssetManager.h index ce0985b38986..376c881ea376 100644 --- a/libs/androidfw/include/androidfw/AssetManager.h +++ b/libs/androidfw/include/androidfw/AssetManager.h @@ -280,21 +280,21 @@ private: ~SharedZip(); private: - SharedZip(const String8& path, time_t modWhen); - SharedZip(int fd, const String8& path); - SharedZip(); // <-- not implemented + SharedZip(const String8& path, ModDate modWhen); + SharedZip(int fd, const String8& path); + SharedZip(); // <-- not implemented - String8 mPath; - ZipFileRO* mZipFile; - time_t mModWhen; + String8 mPath; + ZipFileRO* mZipFile; + ModDate mModWhen; - Asset* mResourceTableAsset; - ResTable* mResourceTable; + Asset* mResourceTableAsset; + ResTable* mResourceTable; - Vector<asset_path> mOverlays; + Vector<asset_path> mOverlays; - static Mutex gLock; - static DefaultKeyedVector<String8, wp<SharedZip> > gOpen; + static Mutex gLock; + static DefaultKeyedVector<String8, wp<SharedZip> > gOpen; }; /* diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h index e213fbd22ab0..ac75eb3bb98c 100644 --- a/libs/androidfw/include/androidfw/Idmap.h +++ b/libs/androidfw/include/androidfw/Idmap.h @@ -25,8 +25,9 @@ #include "android-base/macros.h" #include "android-base/unique_fd.h" #include "androidfw/ConfigDescription.h" -#include "androidfw/StringPiece.h" #include "androidfw/ResourceTypes.h" +#include "androidfw/StringPiece.h" +#include "androidfw/misc.h" #include "utils/ByteOrder.h" namespace android { @@ -213,7 +214,7 @@ class LoadedIdmap { android::base::unique_fd idmap_fd_; std::string_view overlay_apk_path_; std::string_view target_apk_path_; - time_t idmap_last_mod_time_; + ModDate idmap_last_mod_time_; private: DISALLOW_COPY_AND_ASSIGN(LoadedIdmap); diff --git a/libs/androidfw/include/androidfw/TypeWrappers.h b/libs/androidfw/include/androidfw/TypeWrappers.h index fb2fad619011..db641b78a4e4 100644 --- a/libs/androidfw/include/androidfw/TypeWrappers.h +++ b/libs/androidfw/include/androidfw/TypeWrappers.h @@ -27,24 +27,14 @@ struct TypeVariant { class iterator { public: - iterator& operator=(const iterator& rhs) { - mTypeVariant = rhs.mTypeVariant; - mIndex = rhs.mIndex; - return *this; - } - bool operator==(const iterator& rhs) const { return mTypeVariant == rhs.mTypeVariant && mIndex == rhs.mIndex; } - bool operator!=(const iterator& rhs) const { - return mTypeVariant != rhs.mTypeVariant || mIndex != rhs.mIndex; - } - iterator operator++(int) { - uint32_t prevIndex = mIndex; + iterator prev = *this; operator++(); - return iterator(mTypeVariant, prevIndex); + return prev; } const ResTable_entry* operator->() const { @@ -60,18 +50,26 @@ struct TypeVariant { private: friend struct TypeVariant; - iterator(const TypeVariant* tv, uint32_t index) - : mTypeVariant(tv), mIndex(index) {} + + enum class Kind { Begin, End }; + iterator(const TypeVariant* tv, Kind kind) + : mTypeVariant(tv) { + mSparseIndex = mIndex = kind == Kind::Begin ? 0 : tv->mLength; + // mSparseIndex here is technically past the number of sparse entries, but it is still + // ok as it is enough to infer that this is the end iterator. + } + const TypeVariant* mTypeVariant; uint32_t mIndex; + uint32_t mSparseIndex; }; iterator beginEntries() const { - return iterator(this, 0); + return iterator(this, iterator::Kind::Begin); } iterator endEntries() const { - return iterator(this, mLength); + return iterator(this, iterator::Kind::End); } const ResTable_type* data; diff --git a/libs/androidfw/include/androidfw/misc.h b/libs/androidfw/include/androidfw/misc.h index 077609d20d55..c9ba8a01a5e9 100644 --- a/libs/androidfw/include/androidfw/misc.h +++ b/libs/androidfw/include/androidfw/misc.h @@ -13,14 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#pragma once -#include <sys/types.h> +#include <time.h> // // Handy utility functions and portability code. // -#ifndef _LIBS_ANDROID_FW_MISC_H -#define _LIBS_ANDROID_FW_MISC_H namespace android { @@ -41,15 +40,35 @@ typedef enum FileType { } FileType; /* get the file's type; follows symlinks */ FileType getFileType(const char* fileName); -/* get the file's modification date; returns -1 w/errno set on failure */ -time_t getFileModDate(const char* fileName); + +// MinGW doesn't support nanosecond resolution in stat() modification time, and given +// that it only matters on the device it's ok to keep it at a seconds level there. +#ifdef _WIN32 +using ModDate = time_t; +inline constexpr ModDate kInvalidModDate = ModDate(-1); +inline constexpr unsigned long long kModDateResolutionNs = 1ull * 1000 * 1000 * 1000; +inline time_t toTimeT(ModDate m) { + return m; +} +#else +using ModDate = timespec; +inline constexpr ModDate kInvalidModDate = {-1, -1}; +inline constexpr unsigned long long kModDateResolutionNs = 1; +inline time_t toTimeT(ModDate m) { + return m.tv_sec; +} +#endif + +/* get the file's modification date; returns kInvalidModDate w/errno set on failure */ +ModDate getFileModDate(const char* fileName); /* same, but also returns -1 if the file has already been deleted */ -time_t getFileModDate(int fd); +ModDate getFileModDate(int fd); // Check if |path| or |fd| resides on a readonly filesystem. bool isReadonlyFilesystem(const char* path); bool isReadonlyFilesystem(int fd); -}; // namespace android +} // namespace android -#endif // _LIBS_ANDROID_FW_MISC_H +// Whoever uses getFileModDate() will need this as well +bool operator==(const timespec& l, const timespec& r); diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp index 93dcaf549a90..32f3624a3aee 100644 --- a/libs/androidfw/misc.cpp +++ b/libs/androidfw/misc.cpp @@ -28,11 +28,13 @@ #include <sys/vfs.h> #endif // __linux__ -#include <cstring> -#include <cstdio> #include <errno.h> #include <sys/stat.h> +#include <cstdio> +#include <cstring> +#include <tuple> + namespace android { /* @@ -73,27 +75,34 @@ FileType getFileType(const char* fileName) } } -/* - * Get a file's modification date. - */ -time_t getFileModDate(const char* fileName) { - struct stat sb; - if (stat(fileName, &sb) < 0) { - return (time_t)-1; - } - return sb.st_mtime; +static ModDate getModDate(const struct stat& st) { +#ifdef _WIN32 + return st.st_mtime; +#elif defined(__APPLE__) + return st.st_mtimespec; +#else + return st.st_mtim; +#endif } -time_t getFileModDate(int fd) { - struct stat sb; - if (fstat(fd, &sb) < 0) { - return (time_t)-1; - } - if (sb.st_nlink <= 0) { - errno = ENOENT; - return (time_t)-1; - } - return sb.st_mtime; +ModDate getFileModDate(const char* fileName) { + struct stat sb; + if (stat(fileName, &sb) < 0) { + return kInvalidModDate; + } + return getModDate(sb); +} + +ModDate getFileModDate(int fd) { + struct stat sb; + if (fstat(fd, &sb) < 0) { + return kInvalidModDate; + } + if (sb.st_nlink <= 0) { + errno = ENOENT; + return kInvalidModDate; + } + return getModDate(sb); } #ifndef __linux__ @@ -124,4 +133,8 @@ bool isReadonlyFilesystem(int fd) { } #endif // __linux__ -}; // namespace android +} // namespace android + +bool operator==(const timespec& l, const timespec& r) { + return std::tie(l.tv_sec, l.tv_nsec) == std::tie(r.tv_sec, l.tv_nsec); +} diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp index 60aa7d88925d..cb2e56f5f5e4 100644 --- a/libs/androidfw/tests/Idmap_test.cpp +++ b/libs/androidfw/tests/Idmap_test.cpp @@ -14,6 +14,9 @@ * limitations under the License. */ +#include <chrono> +#include <thread> + #include "android-base/file.h" #include "androidfw/ApkAssets.h" #include "androidfw/AssetManager2.h" @@ -27,6 +30,7 @@ #include "data/overlayable/R.h" #include "data/system/R.h" +using namespace std::chrono_literals; using ::testing::NotNull; namespace overlay = com::android::overlay; @@ -218,10 +222,13 @@ TEST_F(IdmapTest, OverlayAssetsIsUpToDate) { unlink(temp_file.path); ASSERT_FALSE(apk_assets->IsUpToDate()); - sleep(2); + + const auto sleep_duration = + std::chrono::nanoseconds(std::max(kModDateResolutionNs, 1'000'000ull)); + std::this_thread::sleep_for(sleep_duration); base::WriteStringToFile("hello", temp_file.path); - sleep(2); + std::this_thread::sleep_for(sleep_duration); ASSERT_FALSE(apk_assets->IsUpToDate()); } diff --git a/libs/androidfw/tests/TypeWrappers_test.cpp b/libs/androidfw/tests/TypeWrappers_test.cpp index ed30904ec179..d66e05805484 100644 --- a/libs/androidfw/tests/TypeWrappers_test.cpp +++ b/libs/androidfw/tests/TypeWrappers_test.cpp @@ -14,28 +14,42 @@ * limitations under the License. */ -#include <algorithm> #include <androidfw/ResourceTypes.h> #include <androidfw/TypeWrappers.h> -#include <utils/String8.h> +#include <androidfw/Util.h> + +#include <optional> +#include <vector> #include <gtest/gtest.h> namespace android { -// create a ResTable_type in memory with a vector of Res_value* -static ResTable_type* createTypeTable(std::vector<Res_value*>& values, - bool compact_entry = false, - bool short_offsets = false) +using ResValueVector = std::vector<std::optional<Res_value>>; + +// create a ResTable_type in memory +static util::unique_cptr<ResTable_type> createTypeTable( + const ResValueVector& in_values, bool compact_entry, bool short_offsets, bool sparse) { + ResValueVector sparse_values; + if (sparse) { + std::ranges::copy_if(in_values, std::back_inserter(sparse_values), + [](auto&& v) { return v.has_value(); }); + } + const ResValueVector& values = sparse ? sparse_values : in_values; + ResTable_type t{}; t.header.type = RES_TABLE_TYPE_TYPE; t.header.headerSize = sizeof(t); t.header.size = sizeof(t); t.id = 1; - t.flags = short_offsets ? ResTable_type::FLAG_OFFSET16 : 0; + t.flags = sparse + ? ResTable_type::FLAG_SPARSE + : short_offsets ? ResTable_type::FLAG_OFFSET16 : 0; - t.header.size += values.size() * (short_offsets ? sizeof(uint16_t) : sizeof(uint32_t)); + t.header.size += values.size() * + (sparse ? sizeof(ResTable_sparseTypeEntry) : + short_offsets ? sizeof(uint16_t) : sizeof(uint32_t)); t.entriesStart = t.header.size; t.entryCount = values.size(); @@ -53,9 +67,18 @@ static ResTable_type* createTypeTable(std::vector<Res_value*>& values, memcpy(p_header, &t, sizeof(t)); size_t i = 0, entry_offset = 0; - uint32_t k = 0; - for (auto const& v : values) { - if (short_offsets) { + uint32_t sparse_index = 0; + + for (auto const& v : in_values) { + if (sparse) { + if (!v) { + ++i; + continue; + } + const auto p = reinterpret_cast<ResTable_sparseTypeEntry*>(p_offsets) + sparse_index++; + p->idx = i; + p->offset = (entry_offset >> 2) & 0xffffu; + } else if (short_offsets) { uint16_t *p = reinterpret_cast<uint16_t *>(p_offsets) + i; *p = v ? (entry_offset >> 2) & 0xffffu : 0xffffu; } else { @@ -83,62 +106,92 @@ static ResTable_type* createTypeTable(std::vector<Res_value*>& values, } i++; } - return reinterpret_cast<ResTable_type*>(data); + return util::unique_cptr<ResTable_type>{reinterpret_cast<ResTable_type*>(data)}; } TEST(TypeVariantIteratorTest, shouldIterateOverTypeWithoutErrors) { - std::vector<Res_value *> values; - - Res_value *v1 = new Res_value{}; - values.push_back(v1); - - values.push_back(nullptr); - - Res_value *v2 = new Res_value{}; - values.push_back(v2); - - Res_value *v3 = new Res_value{ sizeof(Res_value), 0, Res_value::TYPE_STRING, 0x12345678}; - values.push_back(v3); + ResValueVector values; + + values.push_back(std::nullopt); + values.push_back(Res_value{}); + values.push_back(std::nullopt); + values.push_back(Res_value{}); + values.push_back(Res_value{ sizeof(Res_value), 0, Res_value::TYPE_STRING, 0x12345678}); + values.push_back(std::nullopt); + values.push_back(std::nullopt); + values.push_back(std::nullopt); + values.push_back(Res_value{ sizeof(Res_value), 0, Res_value::TYPE_STRING, 0x87654321}); // test for combinations of compact_entry and short_offsets - for (size_t i = 0; i < 4; i++) { - bool compact_entry = i & 0x1, short_offsets = i & 0x2; - ResTable_type* data = createTypeTable(values, compact_entry, short_offsets); - TypeVariant v(data); + for (size_t i = 0; i < 8; i++) { + bool compact_entry = i & 0x1, short_offsets = i & 0x2, sparse = i & 0x4; + auto data = createTypeTable(values, compact_entry, short_offsets, sparse); + TypeVariant v(data.get()); TypeVariant::iterator iter = v.beginEntries(); ASSERT_EQ(uint32_t(0), iter.index()); - ASSERT_TRUE(NULL != *iter); - ASSERT_EQ(uint32_t(0), iter->key()); + ASSERT_TRUE(NULL == *iter); ASSERT_NE(v.endEntries(), iter); - iter++; + ++iter; ASSERT_EQ(uint32_t(1), iter.index()); - ASSERT_TRUE(NULL == *iter); + ASSERT_TRUE(NULL != *iter); + ASSERT_EQ(uint32_t(1), iter->key()); ASSERT_NE(v.endEntries(), iter); iter++; ASSERT_EQ(uint32_t(2), iter.index()); + ASSERT_TRUE(NULL == *iter); + ASSERT_NE(v.endEntries(), iter); + + ++iter; + + ASSERT_EQ(uint32_t(3), iter.index()); ASSERT_TRUE(NULL != *iter); - ASSERT_EQ(uint32_t(2), iter->key()); + ASSERT_EQ(uint32_t(3), iter->key()); ASSERT_NE(v.endEntries(), iter); iter++; - ASSERT_EQ(uint32_t(3), iter.index()); + ASSERT_EQ(uint32_t(4), iter.index()); ASSERT_TRUE(NULL != *iter); ASSERT_EQ(iter->is_compact(), compact_entry); - ASSERT_EQ(uint32_t(3), iter->key()); + ASSERT_EQ(uint32_t(4), iter->key()); ASSERT_EQ(uint32_t(0x12345678), iter->value().data); ASSERT_EQ(Res_value::TYPE_STRING, iter->value().dataType); + ++iter; + + ASSERT_EQ(uint32_t(5), iter.index()); + ASSERT_TRUE(NULL == *iter); + ASSERT_NE(v.endEntries(), iter); + + ++iter; + + ASSERT_EQ(uint32_t(6), iter.index()); + ASSERT_TRUE(NULL == *iter); + ASSERT_NE(v.endEntries(), iter); + + ++iter; + + ASSERT_EQ(uint32_t(7), iter.index()); + ASSERT_TRUE(NULL == *iter); + ASSERT_NE(v.endEntries(), iter); + iter++; - ASSERT_EQ(v.endEntries(), iter); + ASSERT_EQ(uint32_t(8), iter.index()); + ASSERT_TRUE(NULL != *iter); + ASSERT_EQ(iter->is_compact(), compact_entry); + ASSERT_EQ(uint32_t(8), iter->key()); + ASSERT_EQ(uint32_t(0x87654321), iter->value().data); + ASSERT_EQ(Res_value::TYPE_STRING, iter->value().dataType); - free(data); + ++iter; + + ASSERT_EQ(v.endEntries(), iter); } } diff --git a/libs/protoutil/Android.bp b/libs/protoutil/Android.bp index 8af4b7e8f4c8..4fecf4de0312 100644 --- a/libs/protoutil/Android.bp +++ b/libs/protoutil/Android.bp @@ -59,7 +59,6 @@ cc_library { apex_available: [ "//apex_available:platform", "com.android.os.statsd", - "test_com.android.os.statsd", "com.android.uprobestats", ], } diff --git a/location/api/system-current.txt b/location/api/system-current.txt index ba4224137cd4..023bad26e4f5 100644 --- a/location/api/system-current.txt +++ b/location/api/system-current.txt @@ -35,11 +35,11 @@ package android.location { @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class BeidouSatelliteEphemeris implements android.os.Parcelable { method public int describeContents(); - method @IntRange(from=1, to=63) public int getPrn(); method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteClockModel getSatelliteClockModel(); method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteEphemerisTime getSatelliteEphemerisTime(); method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteHealth getSatelliteHealth(); method @NonNull public android.location.KeplerianOrbitModel getSatelliteOrbitModel(); + method @IntRange(from=1, to=63) public int getSvid(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.location.BeidouSatelliteEphemeris> CREATOR; } @@ -104,11 +104,11 @@ package android.location { public static final class BeidouSatelliteEphemeris.Builder { ctor public BeidouSatelliteEphemeris.Builder(); method @NonNull public android.location.BeidouSatelliteEphemeris build(); - method @NonNull public android.location.BeidouSatelliteEphemeris.Builder setPrn(int); method @NonNull public android.location.BeidouSatelliteEphemeris.Builder setSatelliteClockModel(@NonNull android.location.BeidouSatelliteEphemeris.BeidouSatelliteClockModel); method @NonNull public android.location.BeidouSatelliteEphemeris.Builder setSatelliteEphemerisTime(@NonNull android.location.BeidouSatelliteEphemeris.BeidouSatelliteEphemerisTime); method @NonNull public android.location.BeidouSatelliteEphemeris.Builder setSatelliteHealth(@NonNull android.location.BeidouSatelliteEphemeris.BeidouSatelliteHealth); method @NonNull public android.location.BeidouSatelliteEphemeris.Builder setSatelliteOrbitModel(@NonNull android.location.KeplerianOrbitModel); + method @NonNull public android.location.BeidouSatelliteEphemeris.Builder setSvid(int); } public final class CorrelationVector implements android.os.Parcelable { @@ -195,10 +195,10 @@ package android.location { @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GalileoSatelliteEphemeris implements android.os.Parcelable { method public int describeContents(); method @NonNull public java.util.List<android.location.GalileoSatelliteEphemeris.GalileoSatelliteClockModel> getSatelliteClockModels(); - method @IntRange(from=1, to=36) public int getSatelliteCodeNumber(); method @NonNull public android.location.SatelliteEphemerisTime getSatelliteEphemerisTime(); method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth getSatelliteHealth(); method @NonNull public android.location.KeplerianOrbitModel getSatelliteOrbitModel(); + method @IntRange(from=1, to=36) public int getSvid(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.location.GalileoSatelliteEphemeris> CREATOR; } @@ -207,10 +207,10 @@ package android.location { ctor public GalileoSatelliteEphemeris.Builder(); method @NonNull public android.location.GalileoSatelliteEphemeris build(); method @NonNull public android.location.GalileoSatelliteEphemeris.Builder setSatelliteClockModels(@NonNull java.util.List<android.location.GalileoSatelliteEphemeris.GalileoSatelliteClockModel>); - method @NonNull public android.location.GalileoSatelliteEphemeris.Builder setSatelliteCodeNumber(@IntRange(from=1, to=36) int); method @NonNull public android.location.GalileoSatelliteEphemeris.Builder setSatelliteEphemerisTime(@NonNull android.location.SatelliteEphemerisTime); method @NonNull public android.location.GalileoSatelliteEphemeris.Builder setSatelliteHealth(@NonNull android.location.GalileoSatelliteEphemeris.GalileoSvHealth); method @NonNull public android.location.GalileoSatelliteEphemeris.Builder setSatelliteOrbitModel(@NonNull android.location.KeplerianOrbitModel); + method @NonNull public android.location.GalileoSatelliteEphemeris.Builder setSvid(@IntRange(from=1, to=36) int); } public static final class GalileoSatelliteEphemeris.GalileoSatelliteClockModel implements android.os.Parcelable { @@ -243,25 +243,31 @@ package android.location { public static final class GalileoSatelliteEphemeris.GalileoSvHealth implements android.os.Parcelable { method public int describeContents(); - method @IntRange(from=0, to=1) public int getDataValidityStatusE1b(); - method @IntRange(from=0, to=1) public int getDataValidityStatusE5a(); - method @IntRange(from=0, to=1) public int getDataValidityStatusE5b(); - method @IntRange(from=0, to=3) public int getSignalHealthStatusE1b(); - method @IntRange(from=0, to=3) public int getSignalHealthStatusE5a(); - method @IntRange(from=0, to=3) public int getSignalHealthStatusE5b(); + method public int getDataValidityStatusE1b(); + method public int getDataValidityStatusE5a(); + method public int getDataValidityStatusE5b(); + method public int getSignalHealthStatusE1b(); + method public int getSignalHealthStatusE5a(); + method public int getSignalHealthStatusE5b(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.location.GalileoSatelliteEphemeris.GalileoSvHealth> CREATOR; + field public static final int DATA_STATUS_DATA_VALID = 0; // 0x0 + field public static final int DATA_STATUS_WORKING_WITHOUT_GUARANTEE = 1; // 0x1 + field public static final int HEALTH_STATUS_EXTENDED_OPERATION_MODE = 2; // 0x2 + field public static final int HEALTH_STATUS_IN_TEST = 3; // 0x3 + field public static final int HEALTH_STATUS_OK = 0; // 0x0 + field public static final int HEALTH_STATUS_OUT_OF_SERVICE = 1; // 0x1 } public static final class GalileoSatelliteEphemeris.GalileoSvHealth.Builder { ctor public GalileoSatelliteEphemeris.GalileoSvHealth.Builder(); method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth build(); - method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setDataValidityStatusE1b(@IntRange(from=0, to=1) int); - method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setDataValidityStatusE5a(@IntRange(from=0, to=1) int); - method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setDataValidityStatusE5b(@IntRange(from=0, to=1) int); - method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setSignalHealthStatusE1b(@IntRange(from=0, to=3) int); - method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setSignalHealthStatusE5a(@IntRange(from=0, to=3) int); - method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setSignalHealthStatusE5b(@IntRange(from=0, to=3) int); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setDataValidityStatusE1b(int); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setDataValidityStatusE5a(int); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setDataValidityStatusE5b(int); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setSignalHealthStatusE1b(int); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setSignalHealthStatusE5a(int); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setSignalHealthStatusE5b(int); } @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GlonassAlmanac implements android.os.Parcelable { @@ -275,17 +281,19 @@ package android.location { public static final class GlonassAlmanac.GlonassSatelliteAlmanac implements android.os.Parcelable { method public int describeContents(); + method @IntRange(from=1, to=1461) public int getCalendarDayNumber(); method @FloatRange(from=-0.067F, to=0.067f) public double getDeltaI(); method @FloatRange(from=-3600.0F, to=3600.0f) public double getDeltaT(); method @FloatRange(from=-0.004F, to=0.004f) public double getDeltaTDot(); method @FloatRange(from=0.0f, to=0.03f) public double getEccentricity(); - method @IntRange(from=0, to=31) public int getFreqChannel(); + method @IntRange(from=0, to=31) public int getFrequencyChannelNumber(); + method public int getHealthState(); method @FloatRange(from=-1.0F, to=1.0f) public double getLambda(); method @FloatRange(from=-1.0F, to=1.0f) public double getOmega(); method @IntRange(from=1, to=25) public int getSlotNumber(); - method @IntRange(from=0, to=1) public int getSvHealth(); method @FloatRange(from=0.0f, to=44100.0f) public double getTLambda(); method @FloatRange(from=-0.0019F, to=0.0019f) public double getTau(); + method public boolean isGlonassM(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.location.GlonassAlmanac.GlonassSatelliteAlmanac> CREATOR; } @@ -293,15 +301,17 @@ package android.location { public static final class GlonassAlmanac.GlonassSatelliteAlmanac.Builder { ctor public GlonassAlmanac.GlonassSatelliteAlmanac.Builder(); method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac build(); + method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setCalendarDayNumber(@IntRange(from=1, to=1461) int); method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setDeltaI(@FloatRange(from=-0.067F, to=0.067f) double); method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setDeltaT(@FloatRange(from=-3600.0F, to=3600.0f) double); method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setDeltaTDot(@FloatRange(from=-0.004F, to=0.004f) double); method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setEccentricity(@FloatRange(from=0.0f, to=0.03f) double); - method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setFreqChannel(@IntRange(from=0, to=31) int); + method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setFrequencyChannelNumber(@IntRange(from=0, to=31) int); + method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setGlonassM(boolean); + method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setHealthState(int); method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setLambda(@FloatRange(from=-1.0F, to=1.0f) double); method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setOmega(@FloatRange(from=-1.0F, to=1.0f) double); method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setSlotNumber(@IntRange(from=1, to=25) int); - method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setSvHealth(@IntRange(from=0, to=1) int); method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setTLambda(@FloatRange(from=0.0f, to=44100.0f) double); method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setTau(@FloatRange(from=-0.0019F, to=0.0019f) double); } @@ -331,12 +341,17 @@ package android.location { method public int describeContents(); method @IntRange(from=0, to=31) public int getAgeInDays(); method @FloatRange(from=0.0f) public double getFrameTimeSeconds(); - method @IntRange(from=0, to=1) public int getHealthState(); + method public int getHealthState(); method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel getSatelliteClockModel(); method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteOrbitModel getSatelliteOrbitModel(); method @IntRange(from=1, to=25) public int getSlotNumber(); + method @IntRange(from=0, to=60) public int getUpdateIntervalMinutes(); + method public boolean isGlonassM(); + method public boolean isUpdateIntervalOdd(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.location.GlonassSatelliteEphemeris> CREATOR; + field public static final int HEALTH_STATUS_HEALTHY = 0; // 0x0 + field public static final int HEALTH_STATUS_UNHEALTHY = 1; // 0x1 } public static final class GlonassSatelliteEphemeris.Builder { @@ -344,18 +359,23 @@ package android.location { method @NonNull public android.location.GlonassSatelliteEphemeris build(); method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setAgeInDays(@IntRange(from=0, to=31) int); method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setFrameTimeSeconds(@FloatRange(from=0.0f) double); - method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setHealthState(@IntRange(from=0, to=1) int); + method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setGlonassM(boolean); + method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setHealthState(int); method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setSatelliteClockModel(@NonNull android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel); method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setSatelliteOrbitModel(@NonNull android.location.GlonassSatelliteEphemeris.GlonassSatelliteOrbitModel); method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setSlotNumber(@IntRange(from=1, to=25) int); + method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setUpdateIntervalMinutes(@IntRange(from=0, to=60) int); + method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setUpdateIntervalOdd(boolean); } public static final class GlonassSatelliteEphemeris.GlonassSatelliteClockModel implements android.os.Parcelable { method public int describeContents(); method @FloatRange(from=-0.002F, to=0.002f) public double getClockBias(); method @FloatRange(from=-9.32E-10F, to=9.32E-10f) public double getFrequencyBias(); - method @IntRange(from=0xfffffff9, to=6) public int getFrequencyNumber(); + method @IntRange(from=0xfffffff9, to=6) public int getFrequencyChannelNumber(); + method @FloatRange(from=-1.4E-8F, to=1.4E-8f) public double getGroupDelayDiffSeconds(); method @IntRange(from=0) public long getTimeOfClockSeconds(); + method public boolean isGroupDelayDiffSecondsAvailable(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel> CREATOR; } @@ -365,7 +385,9 @@ package android.location { method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel build(); method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel.Builder setClockBias(@FloatRange(from=-0.002F, to=0.002f) double); method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel.Builder setFrequencyBias(@FloatRange(from=-9.32E-10F, to=9.32E-10f) double); - method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel.Builder setFrequencyNumber(@IntRange(from=0xfffffff9, to=6) int); + method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel.Builder setFrequencyChannelNumber(@IntRange(from=0xfffffff9, to=6) int); + method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel.Builder setGroupDelayDiffSeconds(@FloatRange(from=-1.4E-8F, to=1.4E-8f) double); + method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel.Builder setGroupDelayDiffSecondsAvailable(boolean); method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel.Builder setTimeOfClockSeconds(@IntRange(from=0) long); } @@ -401,10 +423,11 @@ package android.location { @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GnssAlmanac implements android.os.Parcelable { method public int describeContents(); method @NonNull public java.util.List<android.location.GnssAlmanac.GnssSatelliteAlmanac> getGnssSatelliteAlmanacs(); - method @IntRange(from=0) public int getIod(); + method @IntRange(from=0) public int getIoda(); method @IntRange(from=0) public long getIssueDateMillis(); method @IntRange(from=0, to=604800) public int getToaSeconds(); method @IntRange(from=0) public int getWeekNumber(); + method public boolean isCompleteAlmanacProvided(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssAlmanac> CREATOR; } @@ -412,8 +435,9 @@ package android.location { public static final class GnssAlmanac.Builder { ctor public GnssAlmanac.Builder(); method @NonNull public android.location.GnssAlmanac build(); + method @NonNull public android.location.GnssAlmanac.Builder setCompleteAlmanacProvided(boolean); method @NonNull public android.location.GnssAlmanac.Builder setGnssSatelliteAlmanacs(@NonNull java.util.List<android.location.GnssAlmanac.GnssSatelliteAlmanac>); - method @NonNull public android.location.GnssAlmanac.Builder setIod(@IntRange(from=0) int); + method @NonNull public android.location.GnssAlmanac.Builder setIoda(@IntRange(from=0) int); method @NonNull public android.location.GnssAlmanac.Builder setIssueDateMillis(@IntRange(from=0) long); method @NonNull public android.location.GnssAlmanac.Builder setToaSeconds(@IntRange(from=0, to=604800) int); method @NonNull public android.location.GnssAlmanac.Builder setWeekNumber(@IntRange(from=0) int); @@ -916,11 +940,11 @@ package android.location { @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GpsSatelliteEphemeris implements android.os.Parcelable { method public int describeContents(); method @NonNull public android.location.GpsSatelliteEphemeris.GpsL2Params getGpsL2Params(); - method @IntRange(from=1, to=32) public int getPrn(); method @NonNull public android.location.GpsSatelliteEphemeris.GpsSatelliteClockModel getSatelliteClockModel(); method @NonNull public android.location.SatelliteEphemerisTime getSatelliteEphemerisTime(); method @NonNull public android.location.GpsSatelliteEphemeris.GpsSatelliteHealth getSatelliteHealth(); method @NonNull public android.location.KeplerianOrbitModel getSatelliteOrbitModel(); + method @IntRange(from=1, to=32) public int getSvid(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.location.GpsSatelliteEphemeris> CREATOR; } @@ -929,11 +953,11 @@ package android.location { ctor public GpsSatelliteEphemeris.Builder(); method @NonNull public android.location.GpsSatelliteEphemeris build(); method @NonNull public android.location.GpsSatelliteEphemeris.Builder setGpsL2Params(@NonNull android.location.GpsSatelliteEphemeris.GpsL2Params); - method @NonNull public android.location.GpsSatelliteEphemeris.Builder setPrn(@IntRange(from=1, to=32) int); method @NonNull public android.location.GpsSatelliteEphemeris.Builder setSatelliteClockModel(@NonNull android.location.GpsSatelliteEphemeris.GpsSatelliteClockModel); method @NonNull public android.location.GpsSatelliteEphemeris.Builder setSatelliteEphemerisTime(@NonNull android.location.SatelliteEphemerisTime); method @NonNull public android.location.GpsSatelliteEphemeris.Builder setSatelliteHealth(@NonNull android.location.GpsSatelliteEphemeris.GpsSatelliteHealth); method @NonNull public android.location.GpsSatelliteEphemeris.Builder setSatelliteOrbitModel(@NonNull android.location.KeplerianOrbitModel); + method @NonNull public android.location.GpsSatelliteEphemeris.Builder setSvid(@IntRange(from=1, to=32) int); } public static final class GpsSatelliteEphemeris.GpsL2Params implements android.os.Parcelable { @@ -1225,11 +1249,11 @@ package android.location { @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class QzssSatelliteEphemeris implements android.os.Parcelable { method public int describeContents(); method @NonNull public android.location.GpsSatelliteEphemeris.GpsL2Params getGpsL2Params(); - method @IntRange(from=183, to=206) public int getPrn(); method @NonNull public android.location.GpsSatelliteEphemeris.GpsSatelliteClockModel getSatelliteClockModel(); method @NonNull public android.location.SatelliteEphemerisTime getSatelliteEphemerisTime(); method @NonNull public android.location.GpsSatelliteEphemeris.GpsSatelliteHealth getSatelliteHealth(); method @NonNull public android.location.KeplerianOrbitModel getSatelliteOrbitModel(); + method @IntRange(from=183, to=206) public int getSvid(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.location.QzssSatelliteEphemeris> CREATOR; } @@ -1238,11 +1262,11 @@ package android.location { ctor public QzssSatelliteEphemeris.Builder(); method @NonNull public android.location.QzssSatelliteEphemeris build(); method @NonNull public android.location.QzssSatelliteEphemeris.Builder setGpsL2Params(@NonNull android.location.GpsSatelliteEphemeris.GpsL2Params); - method @NonNull public android.location.QzssSatelliteEphemeris.Builder setPrn(@IntRange(from=183, to=206) int); method @NonNull public android.location.QzssSatelliteEphemeris.Builder setSatelliteClockModel(@NonNull android.location.GpsSatelliteEphemeris.GpsSatelliteClockModel); method @NonNull public android.location.QzssSatelliteEphemeris.Builder setSatelliteEphemerisTime(@NonNull android.location.SatelliteEphemerisTime); method @NonNull public android.location.QzssSatelliteEphemeris.Builder setSatelliteHealth(@NonNull android.location.GpsSatelliteEphemeris.GpsSatelliteHealth); method @NonNull public android.location.QzssSatelliteEphemeris.Builder setSatelliteOrbitModel(@NonNull android.location.KeplerianOrbitModel); + method @NonNull public android.location.QzssSatelliteEphemeris.Builder setSvid(@IntRange(from=183, to=206) int); } @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class RealTimeIntegrityModel implements android.os.Parcelable { @@ -1435,6 +1459,13 @@ package android.location.provider { field public static final String ACTION_GEOCODE_PROVIDER = "com.android.location.service.GeocodeProvider"; } + @FlaggedApi("android.location.flags.gnss_assistance_interface") public abstract class GnssAssistanceProviderBase { + ctor public GnssAssistanceProviderBase(@NonNull android.content.Context, @NonNull String); + method @NonNull public final android.os.IBinder getBinder(); + method public abstract void onRequest(@NonNull android.os.OutcomeReceiver<android.location.GnssAssistance,java.lang.Throwable>); + field public static final String ACTION_GNSS_ASSISTANCE_PROVIDER = "android.location.provider.action.GNSS_ASSISTANCE_PROVIDER"; + } + public abstract class LocationProviderBase { ctor public LocationProviderBase(@NonNull android.content.Context, @NonNull String, @NonNull android.location.provider.ProviderProperties); method @Nullable public final android.os.IBinder getBinder(); diff --git a/location/java/android/location/BeidouSatelliteEphemeris.java b/location/java/android/location/BeidouSatelliteEphemeris.java index 6bd91e3318c3..3382c20964d9 100644 --- a/location/java/android/location/BeidouSatelliteEphemeris.java +++ b/location/java/android/location/BeidouSatelliteEphemeris.java @@ -35,8 +35,8 @@ import com.android.internal.util.Preconditions; @FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) @SystemApi public final class BeidouSatelliteEphemeris implements Parcelable { - /** The PRN number of the Beidou satellite. */ - private final int mPrn; + /** The PRN or satellite ID number for the Beidou satellite. */ + private final int mSvid; /** Satellite clock model. */ private final BeidouSatelliteClockModel mSatelliteClockModel; @@ -51,8 +51,8 @@ public final class BeidouSatelliteEphemeris implements Parcelable { private final BeidouSatelliteEphemerisTime mSatelliteEphemerisTime; private BeidouSatelliteEphemeris(Builder builder) { - // Allow PRN beyond the range to support potential future extensibility. - Preconditions.checkArgument(builder.mPrn >= 1); + // Allow Svid beyond the range to support potential future extensibility. + Preconditions.checkArgument(builder.mSvid >= 1); Preconditions.checkNotNull(builder.mSatelliteClockModel, "SatelliteClockModel cannot be null"); Preconditions.checkNotNull(builder.mSatelliteOrbitModel, @@ -61,17 +61,17 @@ public final class BeidouSatelliteEphemeris implements Parcelable { "SatelliteHealth cannot be null"); Preconditions.checkNotNull(builder.mSatelliteEphemerisTime, "SatelliteEphemerisTime cannot be null"); - mPrn = builder.mPrn; + mSvid = builder.mSvid; mSatelliteClockModel = builder.mSatelliteClockModel; mSatelliteOrbitModel = builder.mSatelliteOrbitModel; mSatelliteHealth = builder.mSatelliteHealth; mSatelliteEphemerisTime = builder.mSatelliteEphemerisTime; } - /** Returns the PRN of the satellite. */ + /** Returns the PRN or satellite ID number for the Beidou satellite. */ @IntRange(from = 1, to = 63) - public int getPrn() { - return mPrn; + public int getSvid() { + return mSvid; } /** Returns the satellite clock model. */ @@ -105,7 +105,7 @@ public final class BeidouSatelliteEphemeris implements Parcelable { public BeidouSatelliteEphemeris createFromParcel(Parcel in) { final BeidouSatelliteEphemeris.Builder beidouSatelliteEphemeris = new Builder() - .setPrn(in.readInt()) + .setSvid(in.readInt()) .setSatelliteClockModel( in.readTypedObject(BeidouSatelliteClockModel.CREATOR)) .setSatelliteOrbitModel( @@ -131,7 +131,7 @@ public final class BeidouSatelliteEphemeris implements Parcelable { @Override public void writeToParcel(@NonNull Parcel parcel, int flags) { - parcel.writeInt(mPrn); + parcel.writeInt(mSvid); parcel.writeTypedObject(mSatelliteClockModel, flags); parcel.writeTypedObject(mSatelliteOrbitModel, flags); parcel.writeTypedObject(mSatelliteHealth, flags); @@ -142,7 +142,7 @@ public final class BeidouSatelliteEphemeris implements Parcelable { @NonNull public String toString() { StringBuilder builder = new StringBuilder("BeidouSatelliteEphemeris["); - builder.append("prn = ").append(mPrn); + builder.append("svid = ").append(mSvid); builder.append(", satelliteClockModel = ").append(mSatelliteClockModel); builder.append(", satelliteOrbitModel = ").append(mSatelliteOrbitModel); builder.append(", satelliteHealth = ").append(mSatelliteHealth); @@ -153,16 +153,16 @@ public final class BeidouSatelliteEphemeris implements Parcelable { /** Builder for {@link BeidouSatelliteEphemeris} */ public static final class Builder { - private int mPrn; + private int mSvid; private BeidouSatelliteClockModel mSatelliteClockModel; private KeplerianOrbitModel mSatelliteOrbitModel; private BeidouSatelliteHealth mSatelliteHealth; private BeidouSatelliteEphemerisTime mSatelliteEphemerisTime; - /** Sets the PRN of the satellite. */ + /** Sets the PRN or satellite ID number for the Beidou satellite. */ @NonNull - public Builder setPrn(int prn) { - mPrn = prn; + public Builder setSvid(int svid) { + mSvid = svid; return this; } diff --git a/location/java/android/location/GalileoSatelliteEphemeris.java b/location/java/android/location/GalileoSatelliteEphemeris.java index 7dd371176267..08218f4bf83e 100644 --- a/location/java/android/location/GalileoSatelliteEphemeris.java +++ b/location/java/android/location/GalileoSatelliteEphemeris.java @@ -43,8 +43,8 @@ import java.util.List; @SystemApi public final class GalileoSatelliteEphemeris implements Parcelable { - /** Satellite code number. */ - private int mSatelliteCodeNumber; + /** PRN or satellite ID number for the Galileo satellite. */ + private int mSvid; /** Array of satellite clock model. */ @NonNull private final List<GalileoSatelliteClockModel> mSatelliteClockModels; @@ -59,8 +59,8 @@ public final class GalileoSatelliteEphemeris implements Parcelable { @NonNull private final SatelliteEphemerisTime mSatelliteEphemerisTime; private GalileoSatelliteEphemeris(Builder builder) { - // Allow satelliteCodeNumber beyond the range to support potential future extensibility. - Preconditions.checkArgument(builder.mSatelliteCodeNumber >= 1); + // Allow svid beyond the range to support potential future extensibility. + Preconditions.checkArgument(builder.mSvid >= 1); Preconditions.checkNotNull( builder.mSatelliteClockModels, "SatelliteClockModels cannot be null"); Preconditions.checkNotNull( @@ -68,7 +68,7 @@ public final class GalileoSatelliteEphemeris implements Parcelable { Preconditions.checkNotNull(builder.mSatelliteHealth, "SatelliteHealth cannot be null"); Preconditions.checkNotNull( builder.mSatelliteEphemerisTime, "SatelliteEphemerisTime cannot be null"); - mSatelliteCodeNumber = builder.mSatelliteCodeNumber; + mSvid = builder.mSvid; final List<GalileoSatelliteClockModel> satelliteClockModels = builder.mSatelliteClockModels; mSatelliteClockModels = Collections.unmodifiableList(new ArrayList<>(satelliteClockModels)); mSatelliteOrbitModel = builder.mSatelliteOrbitModel; @@ -76,10 +76,10 @@ public final class GalileoSatelliteEphemeris implements Parcelable { mSatelliteEphemerisTime = builder.mSatelliteEphemerisTime; } - /** Returns the satellite code number. */ + /** Returns the PRN or satellite ID number for the Galileo satellite. */ @IntRange(from = 1, to = 36) - public int getSatelliteCodeNumber() { - return mSatelliteCodeNumber; + public int getSvid() { + return mSvid; } /** Returns the list of satellite clock models. */ @@ -113,7 +113,7 @@ public final class GalileoSatelliteEphemeris implements Parcelable { public GalileoSatelliteEphemeris createFromParcel(Parcel in) { final GalileoSatelliteEphemeris.Builder galileoSatelliteEphemeris = new Builder(); - galileoSatelliteEphemeris.setSatelliteCodeNumber(in.readInt()); + galileoSatelliteEphemeris.setSvid(in.readInt()); List<GalileoSatelliteClockModel> satelliteClockModels = new ArrayList<>(); in.readTypedList(satelliteClockModels, GalileoSatelliteClockModel.CREATOR); galileoSatelliteEphemeris.setSatelliteClockModels(satelliteClockModels); @@ -139,7 +139,7 @@ public final class GalileoSatelliteEphemeris implements Parcelable { @Override public void writeToParcel(@NonNull Parcel parcel, int flags) { - parcel.writeInt(mSatelliteCodeNumber); + parcel.writeInt(mSvid); parcel.writeTypedList(mSatelliteClockModels, flags); parcel.writeTypedObject(mSatelliteOrbitModel, flags); parcel.writeTypedObject(mSatelliteHealth, flags); @@ -150,7 +150,7 @@ public final class GalileoSatelliteEphemeris implements Parcelable { @NonNull public String toString() { StringBuilder builder = new StringBuilder("GalileoSatelliteEphemeris["); - builder.append("satelliteCodeNumber = ").append(mSatelliteCodeNumber); + builder.append("svid = ").append(mSvid); builder.append(", satelliteClockModels = ").append(mSatelliteClockModels); builder.append(", satelliteOrbitModel = ").append(mSatelliteOrbitModel); builder.append(", satelliteHealth = ").append(mSatelliteHealth); @@ -161,17 +161,16 @@ public final class GalileoSatelliteEphemeris implements Parcelable { /** Builder for {@link GalileoSatelliteEphemeris}. */ public static final class Builder { - private int mSatelliteCodeNumber; + private int mSvid; private List<GalileoSatelliteClockModel> mSatelliteClockModels; private KeplerianOrbitModel mSatelliteOrbitModel; private GalileoSvHealth mSatelliteHealth; private SatelliteEphemerisTime mSatelliteEphemerisTime; - /** Sets the satellite code number. */ + /** Sets the PRN or satellite ID number for the Galileo satellite. */ @NonNull - public Builder setSatelliteCodeNumber( - @IntRange(from = 1, to = 36) int satelliteCodeNumber) { - mSatelliteCodeNumber = satelliteCodeNumber; + public Builder setSvid(@IntRange(from = 1, to = 36) int svid) { + mSvid = svid; return this; } @@ -218,37 +217,107 @@ public final class GalileoSatelliteEphemeris implements Parcelable { * <p>This is defined in Galileo-OS-SIS-ICD 5.1.9.3. */ public static final class GalileoSvHealth implements Parcelable { + + /** + * Galileo data validity status. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({DATA_STATUS_DATA_VALID, DATA_STATUS_WORKING_WITHOUT_GUARANTEE}) + public @interface GalileoDataValidityStatus {} + + /** + * The following enumerations must be in sync with the values declared in + * GalileoHealthDataVaidityType in GalileoSatelliteEphemeris.aidl. + */ + + /** Data validity status is data valid. */ + public static final int DATA_STATUS_DATA_VALID = 0; + + /** Data validity status is working without guarantee. */ + public static final int DATA_STATUS_WORKING_WITHOUT_GUARANTEE = 1; + + /** + * Galileo signal health status. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + HEALTH_STATUS_OK, + HEALTH_STATUS_OUT_OF_SERVICE, + HEALTH_STATUS_EXTENDED_OPERATION_MODE, + HEALTH_STATUS_IN_TEST + }) + public @interface GalileoHealthStatus {} + + /** + * The following enumerations must be in sync with the values declared in + * GalileoHealthStatusType in GalileoSatelliteEphemeris.aidl. + */ + + /** Health status is ok. */ + public static final int HEALTH_STATUS_OK = 0; + + /** Health status is out of service. */ + public static final int HEALTH_STATUS_OUT_OF_SERVICE = 1; + + /** Health status is in extended operation mode. */ + public static final int HEALTH_STATUS_EXTENDED_OPERATION_MODE = 2; + + /** Health status is in test mode. */ + public static final int HEALTH_STATUS_IN_TEST = 3; + /** E1-B data validity status. */ - private int mDataValidityStatusE1b; + private @GalileoDataValidityStatus int mDataValidityStatusE1b; /** E1-B/C signal health status. */ - private int mSignalHealthStatusE1b; + private @GalileoHealthStatus int mSignalHealthStatusE1b; /** E5a data validity status. */ - private int mDataValidityStatusE5a; + private @GalileoDataValidityStatus int mDataValidityStatusE5a; /** E5a signal health status. */ - private int mSignalHealthStatusE5a; + private @GalileoHealthStatus int mSignalHealthStatusE5a; /** E5b data validity status. */ - private int mDataValidityStatusE5b; + private @GalileoDataValidityStatus int mDataValidityStatusE5b; /** E5b signal health status. */ - private int mSignalHealthStatusE5b; + private @GalileoHealthStatus int mSignalHealthStatusE5b; private GalileoSvHealth(Builder builder) { Preconditions.checkArgumentInRange( - builder.mDataValidityStatusE1b, 0, 1, "DataValidityStatusE1b"); + builder.mDataValidityStatusE1b, + DATA_STATUS_DATA_VALID, + DATA_STATUS_WORKING_WITHOUT_GUARANTEE, + "DataValidityStatusE1b"); Preconditions.checkArgumentInRange( - builder.mSignalHealthStatusE1b, 0, 3, "SignalHealthStatusE1b"); + builder.mSignalHealthStatusE1b, + HEALTH_STATUS_OK, + HEALTH_STATUS_IN_TEST, + "SignalHealthStatusE1b"); Preconditions.checkArgumentInRange( - builder.mDataValidityStatusE5a, 0, 1, "DataValidityStatusE5a"); + builder.mDataValidityStatusE5a, + DATA_STATUS_DATA_VALID, + DATA_STATUS_WORKING_WITHOUT_GUARANTEE, + "DataValidityStatusE5a"); Preconditions.checkArgumentInRange( - builder.mSignalHealthStatusE5a, 0, 3, "SignalHealthStatusE5a"); + builder.mSignalHealthStatusE5a, + HEALTH_STATUS_OK, + HEALTH_STATUS_IN_TEST, + "SignalHealthStatusE5a"); Preconditions.checkArgumentInRange( - builder.mDataValidityStatusE5b, 0, 1, "DataValidityStatusE5b"); + builder.mDataValidityStatusE5b, + DATA_STATUS_DATA_VALID, + DATA_STATUS_WORKING_WITHOUT_GUARANTEE, + "DataValidityStatusE5b"); Preconditions.checkArgumentInRange( - builder.mSignalHealthStatusE5b, 0, 3, "SignalHealthStatusE5b"); + builder.mSignalHealthStatusE5b, + HEALTH_STATUS_OK, + HEALTH_STATUS_IN_TEST, + "SignalHealthStatusE5b"); mDataValidityStatusE1b = builder.mDataValidityStatusE1b; mSignalHealthStatusE1b = builder.mSignalHealthStatusE1b; mDataValidityStatusE5a = builder.mDataValidityStatusE5a; @@ -258,37 +327,37 @@ public final class GalileoSatelliteEphemeris implements Parcelable { } /** Returns the E1-B data validity status. */ - @IntRange(from = 0, to = 1) + @GalileoDataValidityStatus public int getDataValidityStatusE1b() { return mDataValidityStatusE1b; } /** Returns the E1-B/C signal health status. */ - @IntRange(from = 0, to = 3) + @GalileoHealthStatus public int getSignalHealthStatusE1b() { return mSignalHealthStatusE1b; } /** Returns the E5a data validity status. */ - @IntRange(from = 0, to = 1) + @GalileoDataValidityStatus public int getDataValidityStatusE5a() { return mDataValidityStatusE5a; } /** Returns the E5a signal health status. */ - @IntRange(from = 0, to = 3) + @GalileoHealthStatus public int getSignalHealthStatusE5a() { return mSignalHealthStatusE5a; } /** Returns the E5b data validity status. */ - @IntRange(from = 0, to = 1) + @GalileoDataValidityStatus public int getDataValidityStatusE5b() { return mDataValidityStatusE5b; } /** Returns the E5b signal health status. */ - @IntRange(from = 0, to = 3) + @GalileoHealthStatus public int getSignalHealthStatusE5b() { return mSignalHealthStatusE5b; } @@ -355,7 +424,7 @@ public final class GalileoSatelliteEphemeris implements Parcelable { /** Sets the E1-B data validity status. */ @NonNull public Builder setDataValidityStatusE1b( - @IntRange(from = 0, to = 1) int dataValidityStatusE1b) { + @GalileoDataValidityStatus int dataValidityStatusE1b) { mDataValidityStatusE1b = dataValidityStatusE1b; return this; } @@ -363,7 +432,7 @@ public final class GalileoSatelliteEphemeris implements Parcelable { /** Sets the E1-B/C signal health status. */ @NonNull public Builder setSignalHealthStatusE1b( - @IntRange(from = 0, to = 3) int signalHealthStatusE1b) { + @GalileoHealthStatus int signalHealthStatusE1b) { mSignalHealthStatusE1b = signalHealthStatusE1b; return this; } @@ -371,7 +440,7 @@ public final class GalileoSatelliteEphemeris implements Parcelable { /** Sets the E5a data validity status. */ @NonNull public Builder setDataValidityStatusE5a( - @IntRange(from = 0, to = 1) int dataValidityStatusE5a) { + @GalileoDataValidityStatus int dataValidityStatusE5a) { mDataValidityStatusE5a = dataValidityStatusE5a; return this; } @@ -379,7 +448,7 @@ public final class GalileoSatelliteEphemeris implements Parcelable { /** Sets the E5a signal health status. */ @NonNull public Builder setSignalHealthStatusE5a( - @IntRange(from = 0, to = 3) int signalHealthStatusE5a) { + @GalileoHealthStatus int signalHealthStatusE5a) { mSignalHealthStatusE5a = signalHealthStatusE5a; return this; } @@ -387,7 +456,7 @@ public final class GalileoSatelliteEphemeris implements Parcelable { /** Sets the E5b data validity status. */ @NonNull public Builder setDataValidityStatusE5b( - @IntRange(from = 0, to = 1) int dataValidityStatusE5b) { + @GalileoDataValidityStatus int dataValidityStatusE5b) { mDataValidityStatusE5b = dataValidityStatusE5b; return this; } @@ -395,7 +464,7 @@ public final class GalileoSatelliteEphemeris implements Parcelable { /** Sets the E5b signal health status. */ @NonNull public Builder setSignalHealthStatusE5b( - @IntRange(from = 0, to = 3) int signalHealthStatusE5b) { + @GalileoHealthStatus int signalHealthStatusE5b) { mSignalHealthStatusE5b = signalHealthStatusE5b; return this; } diff --git a/location/java/android/location/GlonassAlmanac.java b/location/java/android/location/GlonassAlmanac.java index 861dc5d257c4..37657435b98a 100644 --- a/location/java/android/location/GlonassAlmanac.java +++ b/location/java/android/location/GlonassAlmanac.java @@ -21,6 +21,7 @@ import android.annotation.FloatRange; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.location.GlonassSatelliteEphemeris.GlonassHealthStatus; import android.location.flags.Flags; import android.os.Parcel; import android.os.Parcelable; @@ -121,11 +122,17 @@ public final class GlonassAlmanac implements Parcelable { /** Slot number. */ private final int mSlotNumber; - /** Satellite health information (0=healthy, 1=unhealthy). */ - private final int mSvHealth; + /** Satellite health status. */ + private final @GlonassHealthStatus int mHealthState; /** Frequency channel number. */ - private final int mFreqChannel; + private final int mFrequencyChannelNumber; + + /** Calendar day number within the four-year period beginning since the leap year. */ + private final int mCalendarDayNumber; + + /** Flag to indicates if the satellite is a GLONASS-M satellitee. */ + private final boolean mGlonassM; /** Coarse value of satellite time correction to GLONASS time in seconds. */ private final double mTau; @@ -148,15 +155,18 @@ public final class GlonassAlmanac implements Parcelable { /** Eccentricity. */ private final double mEccentricity; - /** Argument of perigee in radians. */ + /** Argument of perigee in semi-circles. */ private final double mOmega; private GlonassSatelliteAlmanac(Builder builder) { // Allow slotNumber beyond the range to support potential future extensibility. Preconditions.checkArgument(builder.mSlotNumber >= 1); - // Allow svHealth beyond the range to support potential future extensibility. - Preconditions.checkArgument(builder.mSvHealth >= 0); - Preconditions.checkArgumentInRange(builder.mFreqChannel, 0, 31, "FreqChannel"); + // Allow healthState beyond the range to support potential future extensibility. + Preconditions.checkArgument(builder.mHealthState >= 0); + Preconditions.checkArgumentInRange( + builder.mFrequencyChannelNumber, 0, 31, "FrequencyChannelNumber"); + Preconditions.checkArgumentInRange( + builder.mCalendarDayNumber, 1, 1461, "CalendarDayNumber"); Preconditions.checkArgumentInRange(builder.mTau, -1.9e-3f, 1.9e-3f, "Tau"); Preconditions.checkArgumentInRange(builder.mTLambda, 0.0f, 44100.0f, "TLambda"); Preconditions.checkArgumentInRange(builder.mLambda, -1.0f, 1.0f, "Lambda"); @@ -166,8 +176,10 @@ public final class GlonassAlmanac implements Parcelable { Preconditions.checkArgumentInRange(builder.mEccentricity, 0.0f, 0.03f, "Eccentricity"); Preconditions.checkArgumentInRange(builder.mOmega, -1.0f, 1.0f, "Omega"); mSlotNumber = builder.mSlotNumber; - mSvHealth = builder.mSvHealth; - mFreqChannel = builder.mFreqChannel; + mHealthState = builder.mHealthState; + mFrequencyChannelNumber = builder.mFrequencyChannelNumber; + mCalendarDayNumber = builder.mCalendarDayNumber; + mGlonassM = builder.mGlonassM; mTau = builder.mTau; mTLambda = builder.mTLambda; mLambda = builder.mLambda; @@ -184,16 +196,29 @@ public final class GlonassAlmanac implements Parcelable { return mSlotNumber; } - /** Returns the Satellite health information (0=healthy, 1=unhealthy). */ - @IntRange(from = 0, to = 1) - public int getSvHealth() { - return mSvHealth; + /** Returns the satellite health status. */ + public @GlonassHealthStatus int getHealthState() { + return mHealthState; } /** Returns the frequency channel number. */ @IntRange(from = 0, to = 31) - public int getFreqChannel() { - return mFreqChannel; + public int getFrequencyChannelNumber() { + return mFrequencyChannelNumber; + } + + /** + * Returns the calendar day number within the four-year period beginning since the leap + * year. + */ + @IntRange(from = 1, to = 1461) + public int getCalendarDayNumber() { + return mCalendarDayNumber; + } + + /** Returns true if the satellite is a GLONASS-M satellitee, false otherwise. */ + public boolean isGlonassM() { + return mGlonassM; } /** Returns the coarse value of satellite time correction to GLONASS time in seconds. */ @@ -241,7 +266,7 @@ public final class GlonassAlmanac implements Parcelable { return mEccentricity; } - /** Returns the argument of perigee in radians. */ + /** Returns the Argument of perigee in semi-circles. */ @FloatRange(from = -1.0f, to = 1.0f) public double getOmega() { return mOmega; @@ -255,8 +280,10 @@ public final class GlonassAlmanac implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mSlotNumber); - dest.writeInt(mSvHealth); - dest.writeInt(mFreqChannel); + dest.writeInt(mHealthState); + dest.writeInt(mFrequencyChannelNumber); + dest.writeInt(mCalendarDayNumber); + dest.writeBoolean(mGlonassM); dest.writeDouble(mTau); dest.writeDouble(mTLambda); dest.writeDouble(mLambda); @@ -273,8 +300,10 @@ public final class GlonassAlmanac implements Parcelable { public GlonassSatelliteAlmanac createFromParcel(@NonNull Parcel source) { return new GlonassSatelliteAlmanac.Builder() .setSlotNumber(source.readInt()) - .setSvHealth(source.readInt()) - .setFreqChannel(source.readInt()) + .setHealthState(source.readInt()) + .setFrequencyChannelNumber(source.readInt()) + .setCalendarDayNumber(source.readInt()) + .setGlonassM(source.readBoolean()) .setTau(source.readDouble()) .setTLambda(source.readDouble()) .setLambda(source.readDouble()) @@ -297,8 +326,10 @@ public final class GlonassAlmanac implements Parcelable { public String toString() { StringBuilder builder = new StringBuilder("GlonassSatelliteAlmanac["); builder.append("slotNumber = ").append(mSlotNumber); - builder.append(", svHealth = ").append(mSvHealth); - builder.append(", freqChannel = ").append(mFreqChannel); + builder.append(", healthState = ").append(mHealthState); + builder.append(", frequencyChannelNumber = ").append(mFrequencyChannelNumber); + builder.append(", calendarDayNumber = ").append(mCalendarDayNumber); + builder.append(", glonassM = ").append(mGlonassM); builder.append(", tau = ").append(mTau); builder.append(", tLambda = ").append(mTLambda); builder.append(", lambda = ").append(mLambda); @@ -314,8 +345,10 @@ public final class GlonassAlmanac implements Parcelable { /** Builder for {@link GlonassSatelliteAlmanac}. */ public static final class Builder { private int mSlotNumber; - private int mSvHealth; - private int mFreqChannel; + private int mHealthState; + private int mFrequencyChannelNumber; + private int mCalendarDayNumber; + private boolean mGlonassM; private double mTau; private double mTLambda; private double mLambda; @@ -332,17 +365,36 @@ public final class GlonassAlmanac implements Parcelable { return this; } - /** Sets the Satellite health information (0=healthy, 1=unhealthy). */ + /** Sets the satellite health status. */ @NonNull - public Builder setSvHealth(@IntRange(from = 0, to = 1) int svHealth) { - mSvHealth = svHealth; + public Builder setHealthState(@GlonassHealthStatus int healthState) { + mHealthState = healthState; return this; } /** Sets the frequency channel number. */ @NonNull - public Builder setFreqChannel(@IntRange(from = 0, to = 31) int freqChannel) { - mFreqChannel = freqChannel; + public Builder setFrequencyChannelNumber( + @IntRange(from = 0, to = 31) int frequencyChannelNumber) { + mFrequencyChannelNumber = frequencyChannelNumber; + return this; + } + + /** + * Sets the calendar day number within the four-year period beginning since the leap + * year. + */ + @NonNull + public Builder setCalendarDayNumber( + @IntRange(from = 1, to = 1461) int calendarDayNumber) { + mCalendarDayNumber = calendarDayNumber; + return this; + } + + /** Sets to true if the satellite is a GLONASS-M satellitee, false otherwise. */ + @NonNull + public Builder setGlonassM(boolean isGlonassM) { + this.mGlonassM = isGlonassM; return this; } @@ -401,7 +453,7 @@ public final class GlonassAlmanac implements Parcelable { return this; } - /** Sets the argument of perigee in radians. */ + /** Sets the Argument of perigee in semi-circles. */ @NonNull public Builder setOmega(@FloatRange(from = -1.0f, to = 1.0f) double omega) { mOmega = omega; diff --git a/location/java/android/location/GlonassSatelliteEphemeris.java b/location/java/android/location/GlonassSatelliteEphemeris.java index 77a6ebb50cfb..bee6047f71c8 100644 --- a/location/java/android/location/GlonassSatelliteEphemeris.java +++ b/location/java/android/location/GlonassSatelliteEphemeris.java @@ -18,6 +18,7 @@ package android.location; import android.annotation.FlaggedApi; import android.annotation.FloatRange; +import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.SystemApi; @@ -27,6 +28,9 @@ import android.os.Parcelable; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A class contains ephemeris parameters specific to Glonass satellites. * @@ -38,11 +42,31 @@ import com.android.internal.util.Preconditions; @SystemApi public final class GlonassSatelliteEphemeris implements Parcelable { + /** + * Glonass signal health status. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({HEALTH_STATUS_HEALTHY, HEALTH_STATUS_UNHEALTHY}) + public @interface GlonassHealthStatus {} + + /** + * The following enumerations must be in sync with the values declared in + * GlonassSatelliteEphemeris.aidl + */ + + /** Health status is healthy. */ + public static final int HEALTH_STATUS_HEALTHY = 0; + + /** Health status is unhealthy. */ + public static final int HEALTH_STATUS_UNHEALTHY = 1; + /** L1/Satellite system (R), satellite number (slot number in sat. constellation). */ private final int mSlotNumber; - /** Health state (0=healthy, 1=unhealthy). */ - private final int mHealthState; + /** Health state. */ + private final @GlonassHealthStatus int mHealthState; /** Message frame time in seconds of the UTC week (tk+nd*86400). */ private final double mFrameTimeSeconds; @@ -50,6 +74,15 @@ public final class GlonassSatelliteEphemeris implements Parcelable { /** Age of current information in days (E). */ private final int mAgeInDays; + /** Update and validity interval in minutes (P1) */ + private final int mUpdateIntervalMinutes; + + /** Flag to indicate if the update interval is odd or even (P2). */ + private final boolean mUpdateIntervalOdd; + + /** Flag to indicates if the satellite is a Glonass-M satellitee (M). */ + private final boolean mGlonassM; + /** Satellite clock model. */ @NonNull private final GlonassSatelliteClockModel mSatelliteClockModel; @@ -63,6 +96,8 @@ public final class GlonassSatelliteEphemeris implements Parcelable { Preconditions.checkArgument(builder.mHealthState >= 0); Preconditions.checkArgument(builder.mFrameTimeSeconds >= 0.0f); Preconditions.checkArgumentInRange(builder.mAgeInDays, 0, 31, "AgeInDays"); + Preconditions.checkArgumentInRange( + builder.mUpdateIntervalMinutes, 0, 60, "UpdateIntervalMinutes"); Preconditions.checkNotNull( builder.mSatelliteClockModel, "SatelliteClockModel cannot be null"); Preconditions.checkNotNull( @@ -71,6 +106,9 @@ public final class GlonassSatelliteEphemeris implements Parcelable { mHealthState = builder.mHealthState; mFrameTimeSeconds = builder.mFrameTimeSeconds; mAgeInDays = builder.mAgeInDays; + mUpdateIntervalMinutes = builder.mUpdateIntervalMinutes; + mUpdateIntervalOdd = builder.mUpdateIntervalOdd; + mGlonassM = builder.mGlonassM; mSatelliteClockModel = builder.mSatelliteClockModel; mSatelliteOrbitModel = builder.mSatelliteOrbitModel; } @@ -83,9 +121,8 @@ public final class GlonassSatelliteEphemeris implements Parcelable { return mSlotNumber; } - /** Returns the health state (0=healthy, 1=unhealthy). */ - @IntRange(from = 0, to = 1) - public int getHealthState() { + /** Returns the health state. */ + public @GlonassHealthStatus int getHealthState() { return mHealthState; } @@ -101,6 +138,22 @@ public final class GlonassSatelliteEphemeris implements Parcelable { return mAgeInDays; } + /** Returns the update interval in minutes (P1). */ + @IntRange(from = 0, to = 60) + public int getUpdateIntervalMinutes() { + return mUpdateIntervalMinutes; + } + + /** Returns true if the update interval (P2) is odd, false otherwise (P2). */ + public boolean isUpdateIntervalOdd() { + return mUpdateIntervalOdd; + } + + /** Returns true if the satellite is a Glonass-M satellitee (M), false otherwise. */ + public boolean isGlonassM() { + return mGlonassM; + } + /** Returns the satellite clock model. */ @NonNull public GlonassSatelliteClockModel getSatelliteClockModel() { @@ -124,6 +177,9 @@ public final class GlonassSatelliteEphemeris implements Parcelable { dest.writeInt(mHealthState); dest.writeDouble(mFrameTimeSeconds); dest.writeInt(mAgeInDays); + dest.writeInt(mUpdateIntervalMinutes); + dest.writeBoolean(mUpdateIntervalOdd); + dest.writeBoolean(mGlonassM); dest.writeTypedObject(mSatelliteClockModel, flags); dest.writeTypedObject(mSatelliteOrbitModel, flags); } @@ -137,6 +193,9 @@ public final class GlonassSatelliteEphemeris implements Parcelable { .setHealthState(source.readInt()) .setFrameTimeSeconds(source.readDouble()) .setAgeInDays(source.readInt()) + .setUpdateIntervalMinutes(source.readInt()) + .setUpdateIntervalOdd(source.readBoolean()) + .setGlonassM(source.readBoolean()) .setSatelliteClockModel( source.readTypedObject(GlonassSatelliteClockModel.CREATOR)) .setSatelliteOrbitModel( @@ -158,6 +217,9 @@ public final class GlonassSatelliteEphemeris implements Parcelable { builder.append(", healthState = ").append(mHealthState); builder.append(", frameTimeSeconds = ").append(mFrameTimeSeconds); builder.append(", ageInDays = ").append(mAgeInDays); + builder.append(", updateIntervalMinutes = ").append(mUpdateIntervalMinutes); + builder.append(", isUpdateIntervalOdd = ").append(mUpdateIntervalOdd); + builder.append(", isGlonassM = ").append(mGlonassM); builder.append(", satelliteClockModel = ").append(mSatelliteClockModel); builder.append(", satelliteOrbitModel = ").append(mSatelliteOrbitModel); builder.append("]"); @@ -170,6 +232,9 @@ public final class GlonassSatelliteEphemeris implements Parcelable { private int mHealthState; private double mFrameTimeSeconds; private int mAgeInDays; + private int mUpdateIntervalMinutes; + private boolean mUpdateIntervalOdd; + private boolean mGlonassM; private GlonassSatelliteClockModel mSatelliteClockModel; private GlonassSatelliteOrbitModel mSatelliteOrbitModel; @@ -182,9 +247,9 @@ public final class GlonassSatelliteEphemeris implements Parcelable { return this; } - /** Sets the health state (0=healthy, 1=unhealthy). */ + /** Sets the health state. */ @NonNull - public Builder setHealthState(@IntRange(from = 0, to = 1) int healthState) { + public Builder setHealthState(@GlonassHealthStatus int healthState) { mHealthState = healthState; return this; } @@ -219,6 +284,28 @@ public final class GlonassSatelliteEphemeris implements Parcelable { return this; } + /** Sets the update interval in minutes (P1). */ + @NonNull + public Builder setUpdateIntervalMinutes( + @IntRange(from = 0, to = 60) int updateIntervalMinutes) { + mUpdateIntervalMinutes = updateIntervalMinutes; + return this; + } + + /** Sets to true if the update interval (P2) is odd, false otherwise. */ + @NonNull + public Builder setUpdateIntervalOdd(boolean isUpdateIntervalOdd) { + mUpdateIntervalOdd = isUpdateIntervalOdd; + return this; + } + + /** Sets to true if the satellite is a Glonass-M satellitee (M), false otherwise. */ + @NonNull + public Builder setGlonassM(boolean isGlonassM) { + mGlonassM = isGlonassM; + return this; + } + /** Builds a {@link GlonassSatelliteEphemeris}. */ @NonNull public GlonassSatelliteEphemeris build() { @@ -246,19 +333,36 @@ public final class GlonassSatelliteEphemeris implements Parcelable { /** Frequency bias (+GammaN). */ private final double mFrequencyBias; - /** Frequency number. */ - private final int mFrequencyNumber; + /** Frequency channel number. */ + private final int mFrequencyChannelNumber; + + /* L1/L2 group delay difference in seconds (DeltaTau). */ + private final double mGroupDelayDiffSeconds; + + /** + * Whether the L1/L2 group delay difference in seconds (DeltaTau) is available. + * + * <p>It is set to true if available, otherwise false. + */ + private final boolean mGroupDelayDiffSecondsAvailable; private GlonassSatelliteClockModel(Builder builder) { Preconditions.checkArgument(builder.mTimeOfClockSeconds >= 0); Preconditions.checkArgumentInRange(builder.mClockBias, -0.002f, 0.002f, "ClockBias"); Preconditions.checkArgumentInRange( builder.mFrequencyBias, -9.32e-10f, 9.32e-10f, "FrequencyBias"); - Preconditions.checkArgumentInRange(builder.mFrequencyNumber, -7, 6, "FrequencyNumber"); + Preconditions.checkArgumentInRange( + builder.mFrequencyChannelNumber, -7, 6, "FrequencyChannelNumber"); + if (builder.mGroupDelayDiffSecondsAvailable) { + Preconditions.checkArgumentInRange( + builder.mGroupDelayDiffSeconds, -1.4e-8f, 1.4e-8f, "GroupDelayDiffSeconds"); + } mTimeOfClockSeconds = builder.mTimeOfClockSeconds; mClockBias = builder.mClockBias; mFrequencyBias = builder.mFrequencyBias; - mFrequencyNumber = builder.mFrequencyNumber; + mFrequencyChannelNumber = builder.mFrequencyChannelNumber; + mGroupDelayDiffSeconds = builder.mGroupDelayDiffSeconds; + mGroupDelayDiffSecondsAvailable = builder.mGroupDelayDiffSecondsAvailable; } /** Returns the time of clock in seconds (UTC). */ @@ -279,10 +383,21 @@ public final class GlonassSatelliteEphemeris implements Parcelable { return mFrequencyBias; } - /** Returns the frequency number. */ + /** Returns the Frequency channel number. */ @IntRange(from = -7, to = 6) - public int getFrequencyNumber() { - return mFrequencyNumber; + public int getFrequencyChannelNumber() { + return mFrequencyChannelNumber; + } + + /** Returns the L1/L2 group delay difference in seconds (DeltaTau). */ + @FloatRange(from = -1.4e-8f, to = 1.4e-8f) + public double getGroupDelayDiffSeconds() { + return mGroupDelayDiffSeconds; + } + + /** Returns whether the L1/L2 group delay difference in seconds (DeltaTau) is available. */ + public boolean isGroupDelayDiffSecondsAvailable() { + return mGroupDelayDiffSecondsAvailable; } @Override @@ -295,7 +410,9 @@ public final class GlonassSatelliteEphemeris implements Parcelable { dest.writeLong(mTimeOfClockSeconds); dest.writeDouble(mClockBias); dest.writeDouble(mFrequencyBias); - dest.writeInt(mFrequencyNumber); + dest.writeInt(mFrequencyChannelNumber); + dest.writeDouble(mGroupDelayDiffSeconds); + dest.writeBoolean(mGroupDelayDiffSecondsAvailable); } public static final @NonNull Parcelable.Creator<GlonassSatelliteClockModel> CREATOR = @@ -306,7 +423,9 @@ public final class GlonassSatelliteEphemeris implements Parcelable { .setTimeOfClockSeconds(source.readLong()) .setClockBias(source.readDouble()) .setFrequencyBias(source.readDouble()) - .setFrequencyNumber(source.readInt()) + .setFrequencyChannelNumber(source.readInt()) + .setGroupDelayDiffSeconds(source.readDouble()) + .setGroupDelayDiffSecondsAvailable(source.readBoolean()) .build(); } @@ -323,7 +442,10 @@ public final class GlonassSatelliteEphemeris implements Parcelable { builder.append("timeOfClockSeconds = ").append(mTimeOfClockSeconds); builder.append(", clockBias = ").append(mClockBias); builder.append(", frequencyBias = ").append(mFrequencyBias); - builder.append(", frequencyNumber = ").append(mFrequencyNumber); + builder.append(", frequencyChannelNumber = ").append(mFrequencyChannelNumber); + if (mGroupDelayDiffSecondsAvailable) { + builder.append(", groupDelayDiffSeconds = ").append(mGroupDelayDiffSeconds); + } builder.append("]"); return builder.toString(); } @@ -333,7 +455,9 @@ public final class GlonassSatelliteEphemeris implements Parcelable { private long mTimeOfClockSeconds; private double mClockBias; private double mFrequencyBias; - private int mFrequencyNumber; + private double mGroupDelayDiffSeconds; + private int mFrequencyChannelNumber; + private boolean mGroupDelayDiffSecondsAvailable; /** Sets the time of clock in seconds (UTC). */ @NonNull @@ -357,10 +481,27 @@ public final class GlonassSatelliteEphemeris implements Parcelable { return this; } - /** Sets the frequency number. */ + /** Sets the Frequency channel number. */ + @NonNull + public Builder setFrequencyChannelNumber( + @IntRange(from = -7, to = 6) int frequencyChannelNumber) { + mFrequencyChannelNumber = frequencyChannelNumber; + return this; + } + + /** Sets the L1/L2 group delay difference in seconds (DeltaTau). */ + @NonNull + public Builder setGroupDelayDiffSeconds( + @FloatRange(from = -1.4e-8f, to = 1.4e-8f) double groupDelayDiffSeconds) { + mGroupDelayDiffSeconds = groupDelayDiffSeconds; + return this; + } + + /** Sets whether the L1/L2 group delay difference in seconds (DeltaTau) is available. */ @NonNull - public Builder setFrequencyNumber(@IntRange(from = -7, to = 6) int frequencyNumber) { - mFrequencyNumber = frequencyNumber; + public Builder setGroupDelayDiffSecondsAvailable( + boolean isGroupDelayDiffSecondsAvailable) { + mGroupDelayDiffSecondsAvailable = isGroupDelayDiffSecondsAvailable; return this; } diff --git a/location/java/android/location/GnssAlmanac.java b/location/java/android/location/GnssAlmanac.java index 6466e45a965e..c16ad91f130a 100644 --- a/location/java/android/location/GnssAlmanac.java +++ b/location/java/android/location/GnssAlmanac.java @@ -59,7 +59,7 @@ public final class GnssAlmanac implements Parcelable { * * <p>This is unused for GPS/QZSS/Baidou. */ - private final int mIod; + private final int mIoda; /** * Almanac reference week number. @@ -75,20 +75,27 @@ public final class GnssAlmanac implements Parcelable { /** Almanac reference time in seconds. */ private final int mToaSeconds; + /** + * Flag to indicate if the satelliteAlmanacs contains complete GNSS + * constellation indicated by svid. + */ + private final boolean mCompleteAlmanacProvided; + /** The list of GnssSatelliteAlmanacs. */ @NonNull private final List<GnssSatelliteAlmanac> mGnssSatelliteAlmanacs; private GnssAlmanac(Builder builder) { Preconditions.checkArgument(builder.mIssueDateMillis >= 0); - Preconditions.checkArgument(builder.mIod >= 0); + Preconditions.checkArgument(builder.mIoda >= 0); Preconditions.checkArgument(builder.mWeekNumber >= 0); Preconditions.checkArgumentInRange(builder.mToaSeconds, 0, 604800, "ToaSeconds"); Preconditions.checkNotNull( builder.mGnssSatelliteAlmanacs, "GnssSatelliteAlmanacs cannot be null"); mIssueDateMillis = builder.mIssueDateMillis; - mIod = builder.mIod; + mIoda = builder.mIoda; mWeekNumber = builder.mWeekNumber; mToaSeconds = builder.mToaSeconds; + mCompleteAlmanacProvided = builder.mCompleteAlmanacProvided; mGnssSatelliteAlmanacs = Collections.unmodifiableList(new ArrayList<>(builder.mGnssSatelliteAlmanacs)); } @@ -101,8 +108,8 @@ public final class GnssAlmanac implements Parcelable { /** Returns the almanac issue of data. */ @IntRange(from = 0) - public int getIod() { - return mIod; + public int getIoda() { + return mIoda; } /** @@ -125,6 +132,14 @@ public final class GnssAlmanac implements Parcelable { return mToaSeconds; } + /** + * Returns the flag to indicate if the satelliteAlmanacs contains complete GNSS + * constellation indicated by svid. + */ + public boolean isCompleteAlmanacProvided() { + return mCompleteAlmanacProvided; + } + /** Returns the list of GnssSatelliteAlmanacs. */ @NonNull public List<GnssSatelliteAlmanac> getGnssSatelliteAlmanacs() { @@ -139,9 +154,10 @@ public final class GnssAlmanac implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeLong(mIssueDateMillis); - dest.writeInt(mIod); + dest.writeInt(mIoda); dest.writeInt(mWeekNumber); dest.writeInt(mToaSeconds); + dest.writeBoolean(mCompleteAlmanacProvided); dest.writeTypedList(mGnssSatelliteAlmanacs); } @@ -151,9 +167,10 @@ public final class GnssAlmanac implements Parcelable { public GnssAlmanac createFromParcel(Parcel in) { GnssAlmanac.Builder gnssAlmanac = new GnssAlmanac.Builder(); gnssAlmanac.setIssueDateMillis(in.readLong()); - gnssAlmanac.setIod(in.readInt()); + gnssAlmanac.setIoda(in.readInt()); gnssAlmanac.setWeekNumber(in.readInt()); gnssAlmanac.setToaSeconds(in.readInt()); + gnssAlmanac.setCompleteAlmanacProvided(in.readBoolean()); List<GnssSatelliteAlmanac> satelliteAlmanacs = new ArrayList<>(); in.readTypedList(satelliteAlmanacs, GnssSatelliteAlmanac.CREATOR); gnssAlmanac.setGnssSatelliteAlmanacs(satelliteAlmanacs); @@ -170,9 +187,10 @@ public final class GnssAlmanac implements Parcelable { public String toString() { StringBuilder builder = new StringBuilder("GnssAlmanac["); builder.append("issueDateMillis=").append(mIssueDateMillis); - builder.append(", iod=").append(mIod); + builder.append(", ioda=").append(mIoda); builder.append(", weekNumber=").append(mWeekNumber); builder.append(", toaSeconds=").append(mToaSeconds); + builder.append(", completeAlmanacProvided=").append(mCompleteAlmanacProvided); builder.append(", satelliteAlmanacs=").append(mGnssSatelliteAlmanacs); builder.append("]"); return builder.toString(); @@ -181,9 +199,10 @@ public final class GnssAlmanac implements Parcelable { /** Builder for {@link GnssAlmanac}. */ public static final class Builder { private long mIssueDateMillis; - private int mIod; + private int mIoda; private int mWeekNumber; private int mToaSeconds; + private boolean mCompleteAlmanacProvided; private List<GnssSatelliteAlmanac> mGnssSatelliteAlmanacs; /** Sets the almanac issue date in milliseconds (UTC). */ @@ -195,8 +214,8 @@ public final class GnssAlmanac implements Parcelable { /** Sets the almanac issue of data. */ @NonNull - public Builder setIod(@IntRange(from = 0) int iod) { - mIod = iod; + public Builder setIoda(@IntRange(from = 0) int ioda) { + mIoda = ioda; return this; } @@ -222,6 +241,16 @@ public final class GnssAlmanac implements Parcelable { return this; } + /** + * Sets to true if the satelliteAlmanacs contains complete GNSS + * constellation indicated by svid, false otherwise. + */ + @NonNull + public Builder setCompleteAlmanacProvided(boolean isCompleteAlmanacProvided) { + this.mCompleteAlmanacProvided = isCompleteAlmanacProvided; + return this; + } + /** Sets the list of GnssSatelliteAlmanacs. */ @NonNull public Builder setGnssSatelliteAlmanacs( @@ -249,7 +278,7 @@ public final class GnssAlmanac implements Parcelable { * <p>For Galileo, this is defined in Galileo-OS-SIS-ICD-v2.1 section 5.1.10. */ public static final class GnssSatelliteAlmanac implements Parcelable { - /** The PRN number of the GNSS satellite. */ + /** The PRN or satellite ID number for the GNSS satellite. */ private final int mSvid; /** @@ -332,7 +361,7 @@ public final class GnssAlmanac implements Parcelable { mAf1 = builder.mAf1; } - /** Returns the PRN number of the GNSS satellite. */ + /** Returns the PRN or satellite ID number of the GNSS satellite. */ @IntRange(from = 1) public int getSvid() { return mSvid; @@ -503,7 +532,7 @@ public final class GnssAlmanac implements Parcelable { private double mAf0; private double mAf1; - /** Sets the PRN number of the GNSS satellite. */ + /** Sets the PRN or satellite ID number of the GNSS satellite. */ @NonNull public Builder setSvid(@IntRange(from = 1) int svid) { mSvid = svid; diff --git a/location/java/android/location/GpsSatelliteEphemeris.java b/location/java/android/location/GpsSatelliteEphemeris.java index ec6bc59dc69c..0abdc30d2f19 100644 --- a/location/java/android/location/GpsSatelliteEphemeris.java +++ b/location/java/android/location/GpsSatelliteEphemeris.java @@ -37,8 +37,8 @@ import com.android.internal.util.Preconditions; @FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) @SystemApi public final class GpsSatelliteEphemeris implements Parcelable { - /** Satellite PRN */ - private final int mPrn; + /** PRN or satellite ID number for the GPS satellite. */ + private final int mSvid; /** L2 parameters. */ @NonNull private final GpsL2Params mGpsL2Params; @@ -56,8 +56,8 @@ public final class GpsSatelliteEphemeris implements Parcelable { @NonNull private final SatelliteEphemerisTime mSatelliteEphemerisTime; private GpsSatelliteEphemeris(Builder builder) { - // Allow PRN beyond the range to support potential future extensibility. - Preconditions.checkArgument(builder.mPrn >= 1); + // Allow svid beyond the range to support potential future extensibility. + Preconditions.checkArgument(builder.mSvid >= 1); Preconditions.checkNotNull(builder.mGpsL2Params, "GPSL2Params cannot be null"); Preconditions.checkNotNull(builder.mSatelliteClockModel, "SatelliteClockModel cannot be null"); @@ -67,7 +67,7 @@ public final class GpsSatelliteEphemeris implements Parcelable { "SatelliteHealth cannot be null"); Preconditions.checkNotNull(builder.mSatelliteEphemerisTime, "SatelliteEphemerisTime cannot be null"); - mPrn = builder.mPrn; + mSvid = builder.mSvid; mGpsL2Params = builder.mGpsL2Params; mSatelliteClockModel = builder.mSatelliteClockModel; mSatelliteOrbitModel = builder.mSatelliteOrbitModel; @@ -75,10 +75,10 @@ public final class GpsSatelliteEphemeris implements Parcelable { mSatelliteEphemerisTime = builder.mSatelliteEphemerisTime; } - /** Returns the PRN of the satellite. */ + /** Returns the svid of the satellite. */ @IntRange(from = 1, to = 32) - public int getPrn() { - return mPrn; + public int getSvid() { + return mSvid; } /** Returns the L2 parameters of the satellite. */ @@ -118,7 +118,7 @@ public final class GpsSatelliteEphemeris implements Parcelable { public GpsSatelliteEphemeris createFromParcel(Parcel in) { final GpsSatelliteEphemeris.Builder gpsSatelliteEphemeris = new Builder() - .setPrn(in.readInt()) + .setSvid(in.readInt()) .setGpsL2Params(in.readTypedObject(GpsL2Params.CREATOR)) .setSatelliteClockModel( in.readTypedObject(GpsSatelliteClockModel.CREATOR)) @@ -144,7 +144,7 @@ public final class GpsSatelliteEphemeris implements Parcelable { @Override public void writeToParcel(@NonNull Parcel parcel, int flags) { - parcel.writeInt(mPrn); + parcel.writeInt(mSvid); parcel.writeTypedObject(mGpsL2Params, flags); parcel.writeTypedObject(mSatelliteClockModel, flags); parcel.writeTypedObject(mSatelliteOrbitModel, flags); @@ -156,7 +156,7 @@ public final class GpsSatelliteEphemeris implements Parcelable { @NonNull public String toString() { StringBuilder builder = new StringBuilder("GpsSatelliteEphemeris["); - builder.append("prn = ").append(mPrn); + builder.append("Svid = ").append(mSvid); builder.append(", gpsL2Params = ").append(mGpsL2Params); builder.append(", satelliteClockModel = ").append(mSatelliteClockModel); builder.append(", satelliteOrbitModel = ").append(mSatelliteOrbitModel); @@ -168,17 +168,17 @@ public final class GpsSatelliteEphemeris implements Parcelable { /** Builder for {@link GpsSatelliteEphemeris} */ public static final class Builder { - private int mPrn = 0; + private int mSvid = 0; private GpsL2Params mGpsL2Params; private GpsSatelliteClockModel mSatelliteClockModel; private KeplerianOrbitModel mSatelliteOrbitModel; private GpsSatelliteHealth mSatelliteHealth; private SatelliteEphemerisTime mSatelliteEphemerisTime; - /** Sets the PRN of the satellite. */ + /** Sets the PRN or satellite ID number for the GPS satellite.. */ @NonNull - public Builder setPrn(@IntRange(from = 1, to = 32) int prn) { - mPrn = prn; + public Builder setSvid(@IntRange(from = 1, to = 32) int svid) { + mSvid = svid; return this; } diff --git a/location/java/android/location/QzssSatelliteEphemeris.java b/location/java/android/location/QzssSatelliteEphemeris.java index 96203d9588c8..dd9f408f53be 100644 --- a/location/java/android/location/QzssSatelliteEphemeris.java +++ b/location/java/android/location/QzssSatelliteEphemeris.java @@ -39,8 +39,8 @@ import com.android.internal.util.Preconditions; @FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) @SystemApi public final class QzssSatelliteEphemeris implements Parcelable { - /** Satellite PRN. */ - private final int mPrn; + /** PRN or satellite ID number for the Qzss satellite. */ + private final int mSvid; /** L2 parameters. */ @NonNull private final GpsL2Params mGpsL2Params; @@ -57,10 +57,10 @@ public final class QzssSatelliteEphemeris implements Parcelable { /** Ephemeris time. */ @NonNull private final SatelliteEphemerisTime mSatelliteEphemerisTime; - /** Returns the PRN of the satellite. */ + /** Returns the PRN or satellite ID number for the Qzss satellite. */ @IntRange(from = 183, to = 206) - public int getPrn() { - return mPrn; + public int getSvid() { + return mSvid; } /** Returns the L2 parameters of the satellite. */ @@ -95,7 +95,7 @@ public final class QzssSatelliteEphemeris implements Parcelable { @Override public void writeToParcel(@NonNull Parcel parcel, int flags) { - parcel.writeInt(mPrn); + parcel.writeInt(mSvid); parcel.writeTypedObject(mGpsL2Params, flags); parcel.writeTypedObject(mSatelliteClockModel, flags); parcel.writeTypedObject(mSatelliteOrbitModel, flags); @@ -104,8 +104,8 @@ public final class QzssSatelliteEphemeris implements Parcelable { } private QzssSatelliteEphemeris(Builder builder) { - // Allow PRN beyond the range to support potential future extensibility. - Preconditions.checkArgument(builder.mPrn >= 1); + // Allow Svid beyond the range to support potential future extensibility. + Preconditions.checkArgument(builder.mSvid >= 1); Preconditions.checkNotNull(builder.mGpsL2Params, "GpsL2Params cannot be null"); Preconditions.checkNotNull(builder.mSatelliteClockModel, "SatelliteClockModel cannot be null"); @@ -115,7 +115,7 @@ public final class QzssSatelliteEphemeris implements Parcelable { "SatelliteHealth cannot be null"); Preconditions.checkNotNull(builder.mSatelliteEphemerisTime, "SatelliteEphemerisTime cannot be null"); - mPrn = builder.mPrn; + mSvid = builder.mSvid; mGpsL2Params = builder.mGpsL2Params; mSatelliteClockModel = builder.mSatelliteClockModel; mSatelliteOrbitModel = builder.mSatelliteOrbitModel; @@ -130,7 +130,7 @@ public final class QzssSatelliteEphemeris implements Parcelable { public QzssSatelliteEphemeris createFromParcel(Parcel in) { final QzssSatelliteEphemeris.Builder qzssSatelliteEphemeris = new Builder() - .setPrn(in.readInt()) + .setSvid(in.readInt()) .setGpsL2Params(in.readTypedObject(GpsL2Params.CREATOR)) .setSatelliteClockModel( in.readTypedObject(GpsSatelliteClockModel.CREATOR)) @@ -158,7 +158,7 @@ public final class QzssSatelliteEphemeris implements Parcelable { @NonNull public String toString() { StringBuilder builder = new StringBuilder("QzssSatelliteEphemeris["); - builder.append("prn=").append(mPrn); + builder.append("Svid=").append(mSvid); builder.append(", gpsL2Params=").append(mGpsL2Params); builder.append(", satelliteClockModel=").append(mSatelliteClockModel); builder.append(", satelliteOrbitModel=").append(mSatelliteOrbitModel); @@ -170,17 +170,17 @@ public final class QzssSatelliteEphemeris implements Parcelable { /** Builder for {@link QzssSatelliteEphemeris}. */ public static final class Builder { - private int mPrn; + private int mSvid; private GpsL2Params mGpsL2Params; private GpsSatelliteClockModel mSatelliteClockModel; private KeplerianOrbitModel mSatelliteOrbitModel; private GpsSatelliteHealth mSatelliteHealth; private SatelliteEphemerisTime mSatelliteEphemerisTime; - /** Sets the PRN of the satellite. */ + /** Sets the PRN or satellite ID number for the Qzss satellite. */ @NonNull - public Builder setPrn(@IntRange(from = 183, to = 206) int prn) { - mPrn = prn; + public Builder setSvid(@IntRange(from = 183, to = 206) int svid) { + mSvid = svid; return this; } diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig index c02cc808d60c..1b38982f48c1 100644 --- a/location/java/android/location/flags/location.aconfig +++ b/location/java/android/location/flags/location.aconfig @@ -167,4 +167,4 @@ flag { namespace: "location" description: "Flag for GNSS assistance interface" bug: "209078566" -}
\ No newline at end of file +} diff --git a/location/java/android/location/provider/GnssAssistanceProviderBase.java b/location/java/android/location/provider/GnssAssistanceProviderBase.java new file mode 100644 index 000000000000..f4b26d5033a5 --- /dev/null +++ b/location/java/android/location/provider/GnssAssistanceProviderBase.java @@ -0,0 +1,152 @@ +/* + * 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.location.provider; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.Context; +import android.content.Intent; +import android.location.GnssAssistance; +import android.location.flags.Flags; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.OutcomeReceiver; +import android.os.RemoteException; +import android.util.Log; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + + +/** + * Base class for GNSS assistance providers outside the system server. + * + * <p>GNSS assistance providers should be wrapped in a non-exported service which returns the result + * of {@link #getBinder()} from the service's {@link android.app.Service#onBind(Intent)} method. The + * service should not be exported so that components other than the system server cannot bind to it. + * Alternatively, the service may be guarded by a permission that only system server can obtain. The + * service may specify metadata on its capabilities: + * + * <ul> + * <li>"serviceVersion": An integer version code to help tie break if multiple services are + * capable of implementing the geocode provider. All else equal, the service with the highest + * version code will be chosen. Assumed to be 0 if not specified. + * <li>"serviceIsMultiuser": A boolean property, indicating if the service wishes to take + * responsibility for handling changes to the current user on the device. If true, the service + * will always be bound from the system user. If false, the service will always be bound from + * the current user. If the current user changes, the old binding will be released, and a new + * binding established under the new user. Assumed to be false if not specified. + * </ul> + * + * <p>The service should have an intent filter in place for the GNSS assistance provider as + * specified by the constant in this class. + * + * <p>GNSS assistance providers are identified by their UID / package name / attribution tag. Based + * on this identity, geocode providers may be given some special privileges. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public abstract class GnssAssistanceProviderBase { + + /** + * The action the wrapping service should have in its intent filter to implement the GNSS + * Assistance provider. + */ + public static final String ACTION_GNSS_ASSISTANCE_PROVIDER = + "android.location.provider.action.GNSS_ASSISTANCE_PROVIDER"; + + final String mTag; + @Nullable + final String mAttributionTag; + final IBinder mBinder; + + /** + * Subclasses should pass in a context and an arbitrary tag that may be used for logcat logging + * of errors, and thus should uniquely identify the class. + */ + public GnssAssistanceProviderBase(@NonNull Context context, @NonNull String tag) { + mTag = tag; + mAttributionTag = context.getAttributionTag(); + mBinder = new GnssAssistanceProviderBase.Service(); + } + + /** + * Returns the IBinder instance that should be returned from the {@link + * android.app.Service#onBind(Intent)} method of the wrapping service. + */ + @NonNull + public final IBinder getBinder() { + return mBinder; + } + + /** + * Requests GNSS assistance data of the given arguments. The given callback must be invoked + * once. + */ + public abstract void onRequest( + @NonNull OutcomeReceiver<GnssAssistance, Throwable> callback); + + private class Service extends IGnssAssistanceProvider.Stub { + @Override + public void request(IGnssAssistanceCallback callback) { + try { + onRequest(new GnssAssistanceProviderBase.SingleUseCallback(callback)); + } catch (RuntimeException e) { + // exceptions on one-way binder threads are dropped - move to a different thread + Log.w(mTag, e); + new Handler(Looper.getMainLooper()) + .post( + () -> { + throw new AssertionError(e); + }); + } + } + } + + private static class SingleUseCallback implements + OutcomeReceiver<GnssAssistance, Throwable> { + + private final AtomicReference<IGnssAssistanceCallback> mCallback; + + SingleUseCallback(IGnssAssistanceCallback callback) { + mCallback = new AtomicReference<>(callback); + } + + @Override + public void onError(Throwable e) { + try { + Objects.requireNonNull(mCallback.getAndSet(null)).onError(); + } catch (RemoteException r) { + throw r.rethrowFromSystemServer(); + } + } + + @Override + public void onResult(GnssAssistance result) { + try { + Objects.requireNonNull(mCallback.getAndSet(null)).onResult(result); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/VisualInterruptionDecisionProviderKosmos.kt b/location/java/android/location/provider/IGnssAssistanceCallback.aidl index 360e9e93f18d..ea38d08df6c2 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/VisualInterruptionDecisionProviderKosmos.kt +++ b/location/java/android/location/provider/IGnssAssistanceCallback.aidl @@ -14,11 +14,15 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification +package android.location.provider; -import com.android.systemui.kosmos.Kosmos -import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider -import org.mockito.kotlin.mock +import android.location.GnssAssistance; -val Kosmos.visualInterruptionDecisionProvider by - Kosmos.Fixture { mock<VisualInterruptionDecisionProvider>() } +/** + * Binder interface for GNSS assistance callbacks. + * @hide + */ +oneway interface IGnssAssistanceCallback { + void onError(); + void onResult(in GnssAssistance result); +} diff --git a/location/java/android/location/provider/IGnssAssistanceProvider.aidl b/location/java/android/location/provider/IGnssAssistanceProvider.aidl new file mode 100644 index 000000000000..1796e9edb347 --- /dev/null +++ b/location/java/android/location/provider/IGnssAssistanceProvider.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location.provider; + +import android.location.provider.IGnssAssistanceCallback; + +/** + * Binder interface for services that implement GNSS assistance providers. Do not implement this + * directly, extend {@link GnssAssistanceProviderBase} instead. + * @hide + */ +oneway interface IGnssAssistanceProvider { + void request(in IGnssAssistanceCallback callback); +} diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index 09f40e005b4c..f42017dc835a 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -226,6 +226,10 @@ public abstract class MediaRoute2ProviderService extends Service { @GuardedBy("mSessionLock") private final ArrayMap<String, MediaStreams> mOngoingMediaStreams = new ArrayMap<>(); + @GuardedBy("mSessionLock") + private final ArrayMap<String, RoutingSessionInfo> mPendingSystemSessionReleases = + new ArrayMap<>(); + public MediaRoute2ProviderService() { mHandler = new Handler(Looper.getMainLooper()); } @@ -358,7 +362,9 @@ public abstract class MediaRoute2ProviderService extends Service { * @return a {@link MediaStreams} instance that holds the media streams to route as part of the * newly created routing session. May be null if system media capture failed, in which case * you can ignore the return value, as you will receive a call to {@link #onReleaseSession} - * where you can clean up this session + * where you can clean up this session. {@link AudioRecord#startRecording()} must be called + * immediately on {@link MediaStreams#getAudioRecord()} after calling this method, in order + * to start streaming audio to the receiver. * @hide */ // TODO: b/362507305 - Unhide once the implementation and CTS are in place. @@ -417,7 +423,7 @@ public abstract class MediaRoute2ProviderService extends Service { } AudioFormat audioFormat = formats.mAudioFormat; - var mediaStreamsBuilder = new MediaStreams.Builder(); + var mediaStreamsBuilder = new MediaStreams.Builder(sessionInfo); if (audioFormat != null) { populateAudioStream(audioFormat, uid, mediaStreamsBuilder); } @@ -458,7 +464,6 @@ public abstract class MediaRoute2ProviderService extends Service { if (uid != Process.INVALID_UID) { audioMixingRuleBuilder.addMixRule(AudioMixingRule.RULE_MATCH_UID, uid); } - AudioMix mix = new AudioMix.Builder(audioMixingRuleBuilder.build()) .setFormat(audioFormat) @@ -471,7 +476,11 @@ public abstract class MediaRoute2ProviderService extends Service { Log.e(TAG, "Couldn't fetch the audio manager."); return; } - audioManager.registerAudioPolicy(audioPolicy); + int audioPolicyResult = audioManager.registerAudioPolicy(audioPolicy); + if (audioPolicyResult != AudioManager.SUCCESS) { + Log.e(TAG, "Failed to register the audio policy."); + return; + } var audioRecord = audioPolicy.createAudioRecordSink(mix); if (audioRecord == null) { Log.e(TAG, "Audio record creation failed."); @@ -521,8 +530,14 @@ public abstract class MediaRoute2ProviderService extends Service { RoutingSessionInfo sessionInfo; synchronized (mSessionLock) { sessionInfo = mSessionInfos.remove(sessionId); - maybeReleaseMediaStreams(sessionId); - + if (Flags.enableMirroringInMediaRouter2()) { + if (sessionInfo == null) { + sessionInfo = maybeReleaseMediaStreams(sessionId); + } + if (sessionInfo == null) { + sessionInfo = mPendingSystemSessionReleases.remove(sessionId); + } + } if (sessionInfo == null) { Log.w(TAG, "notifySessionReleased: Ignoring unknown session info."); return; @@ -539,18 +554,26 @@ public abstract class MediaRoute2ProviderService extends Service { } } - /** Releases any system media routing resources associated with the given {@code sessionId}. */ - private void maybeReleaseMediaStreams(String sessionId) { + /** + * Releases any system media routing resources associated with the given {@code sessionId}. + * + * @return The {@link RoutingSessionInfo} that corresponds to the released media streams, or + * null if no streams were released. + */ + @Nullable + private RoutingSessionInfo maybeReleaseMediaStreams(String sessionId) { if (!Flags.enableMirroringInMediaRouter2()) { - return; + return null; } synchronized (mSessionLock) { var streams = mOngoingMediaStreams.remove(sessionId); if (streams != null) { releaseAudioStream(streams.mAudioPolicy, streams.mAudioRecord); // TODO: b/380431086: Release the video stream once implemented. + return streams.mSessionInfo; } } + return null; } // We cannot reach the code that requires MODIFY_AUDIO_ROUTING without holding it. @@ -1019,12 +1042,16 @@ public abstract class MediaRoute2ProviderService extends Service { if (!checkCallerIsSystem()) { return; } - if (!checkSessionIdIsValid(sessionId, "releaseSession")) { - return; + synchronized (mSessionLock) { + // We proactively release the system media routing session resources when the + // system requests it, to ensure it happens immediately. + RoutingSessionInfo releasedSession = maybeReleaseMediaStreams(sessionId); + if (releasedSession != null) { + mPendingSystemSessionReleases.put(sessionId, releasedSession); + } else if (!checkSessionIdIsValid(sessionId, "releaseSession")) { + return; + } } - // We proactively release the system media routing once the system requests it, to - // ensure it happens immediately. - maybeReleaseMediaStreams(sessionId); addRequestId(requestId); mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onReleaseSession, @@ -1047,9 +1074,19 @@ public abstract class MediaRoute2ProviderService extends Service { @Nullable private final AudioPolicy mAudioPolicy; @Nullable private final AudioRecord mAudioRecord; + /** + * Holds the last {@link RoutingSessionInfo} associated with these streams. + * + * @hide + */ + @GuardedBy("MediaRoute2ProviderService.this.mSessionLock") + @NonNull + private RoutingSessionInfo mSessionInfo; + // TODO: b/380431086: Add the video equivalent. private MediaStreams(Builder builder) { + this.mSessionInfo = builder.mSessionInfo; this.mAudioPolicy = builder.mAudioPolicy; this.mAudioRecord = builder.mAudioRecord; } @@ -1070,9 +1107,19 @@ public abstract class MediaRoute2ProviderService extends Service { */ public static final class Builder { + @NonNull private RoutingSessionInfo mSessionInfo; @Nullable private AudioPolicy mAudioPolicy; @Nullable private AudioRecord mAudioRecord; + /** + * Constructor. + * + * @param sessionInfo The {@link RoutingSessionInfo} associated with these streams. + */ + Builder(@NonNull RoutingSessionInfo sessionInfo) { + mSessionInfo = requireNonNull(sessionInfo); + } + /** Populates system media audio-related structures. */ public Builder setAudioStream( @NonNull AudioPolicy audioPolicy, @NonNull AudioRecord audioRecord) { diff --git a/nfc/tests/src/android/nfc/tech/NfcATest.java b/nfc/tests/src/android/nfc/tech/NfcATest.java new file mode 100644 index 000000000000..40076ebd0a0a --- /dev/null +++ b/nfc/tests/src/android/nfc/tech/NfcATest.java @@ -0,0 +1,185 @@ +/* + * 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.nfc.tech; + +import static android.nfc.tech.NfcA.EXTRA_ATQA; +import static android.nfc.tech.NfcA.EXTRA_SAK; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.nfc.ErrorCodes; +import android.nfc.INfcTag; +import android.nfc.Tag; +import android.nfc.TransceiveResult; +import android.os.Bundle; +import android.os.RemoteException; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.IOException; + +public class NfcATest { + @Mock + private Tag mMockTag; + @Mock + private INfcTag mMockTagService; + @Mock + private Bundle mMockBundle; + private NfcA mNfcA; + private final byte[] mSampleArray = new byte[] {1, 2, 3}; + + @Before + public void setUp() throws RemoteException { + MockitoAnnotations.initMocks(this); + when(mMockBundle.getShort(EXTRA_SAK)).thenReturn((short) 1); + when(mMockBundle.getByteArray(EXTRA_ATQA)).thenReturn(mSampleArray); + when(mMockTag.getTechExtras(TagTechnology.NFC_A)).thenReturn(mMockBundle); + + mNfcA = new NfcA(mMockTag); + } + + @Test + public void testGetNfcAWithTech() { + Tag mockTag = mock(Tag.class); + when(mockTag.getTechExtras(TagTechnology.NFC_A)).thenReturn(mMockBundle); + when(mockTag.hasTech(TagTechnology.NFC_A)).thenReturn(true); + + assertNotNull(NfcA.get(mockTag)); + verify(mockTag).getTechExtras(TagTechnology.NFC_A); + verify(mockTag).hasTech(TagTechnology.NFC_A); + } + + @Test + public void testGetNfcAWithoutTech() { + when(mMockTag.hasTech(TagTechnology.NFC_A)).thenReturn(false); + assertNull(NfcA.get(mMockTag)); + } + + @Test + public void testGetAtga() { + assertNotNull(mNfcA.getAtqa()); + } + + @Test + public void testGetSak() { + assertEquals((short) 1, mNfcA.getSak()); + } + + @Test + public void testTransceive() throws IOException, RemoteException { + TransceiveResult mockTransceiveResult = mock(TransceiveResult.class); + when(mMockTag.getConnectedTechnology()).thenReturn(TagTechnology.NFC_A); + when(mMockTag.getTagService()).thenReturn(mMockTagService); + when(mMockTag.getServiceHandle()).thenReturn(1); + when(mMockTagService.transceive(1, mSampleArray, true)) + .thenReturn(mockTransceiveResult); + when(mockTransceiveResult.getResponseOrThrow()).thenReturn(mSampleArray); + + mNfcA.transceive(mSampleArray); + verify(mMockTag).getTagService(); + verify(mMockTag).getServiceHandle(); + } + + @Test + public void testGetMaxTransceiveLength() throws RemoteException { + when(mMockTag.getTagService()).thenReturn(mMockTagService); + when(mMockTagService.getMaxTransceiveLength(TagTechnology.NFC_A)).thenReturn(1); + + mNfcA.getMaxTransceiveLength(); + verify(mMockTag).getTagService(); + } + + @Test + public void testSetTimeout() { + when(mMockTag.getTagService()).thenReturn(mMockTagService); + try { + when(mMockTagService.setTimeout(TagTechnology.NFC_A, 1000)).thenReturn( + ErrorCodes.SUCCESS); + + mNfcA.setTimeout(1000); + verify(mMockTag).getTagService(); + verify(mMockTagService).setTimeout(TagTechnology.NFC_A, 1000); + } catch (Exception e) { + fail("Unexpected exception during valid setTimeout: " + e.getMessage()); + } + } + + @Test + public void testSetTimeoutInvalidTimeout() { + when(mMockTag.getTagService()).thenReturn(mMockTagService); + try { + when(mMockTagService.setTimeout(TagTechnology.NFC_A, -1)).thenReturn( + ErrorCodes.ERROR_TIMEOUT); + + assertThrows(IllegalArgumentException.class, () -> mNfcA.setTimeout(-1)); + } catch (Exception e) { + fail("Unexpected exception during invalid setTimeout: " + e.getMessage()); + } + } + + @Test + public void testSetTimeoutRemoteException() { + when(mMockTag.getTagService()).thenReturn(mMockTagService); + try { + when(mMockTagService.setTimeout(TagTechnology.NFC_A, 1000)).thenThrow( + new RemoteException()); + + mNfcA.setTimeout(1000); // Should not throw an exception but log it + verify(mMockTag).getTagService(); + verify(mMockTagService).setTimeout(TagTechnology.NFC_A, 1000); + } catch (Exception e) { + fail("Unexpected exception during RemoteException in setTimeout: " + e.getMessage()); + } + + } + + @Test + public void testGetTimeout() { + when(mMockTag.getTagService()).thenReturn(mMockTagService); + try { + when(mMockTagService.getTimeout(TagTechnology.NFC_A)).thenReturn(2000); + + assertEquals(2000, mNfcA.getTimeout()); + verify(mMockTag).getTagService(); + verify(mMockTagService).getTimeout(TagTechnology.NFC_A); + } catch (Exception e) { + fail("Unexpected exception during valid getTimeout: " + e.getMessage()); + } + } + + @Test + public void testGetTimeoutRemoteException() { + when(mMockTag.getTagService()).thenReturn(mMockTagService); + try { + when(mMockTagService.getTimeout(TagTechnology.NFC_A)).thenThrow(new RemoteException()); + + assertEquals(0, mNfcA.getTimeout()); + } catch (Exception e) { + fail("Unexpected exception during RemoteException in getTimeout: " + e.getMessage()); + } + } +} diff --git a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java index 31e1eb36ad8d..70f5bb32a5d5 100644 --- a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java +++ b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java @@ -2074,15 +2074,19 @@ public class PackageWatchdog { bootMitigationCounts.put(observer.name, observer.getBootMitigationCount()); } + FileOutputStream fileStream = null; + ObjectOutputStream objectStream = null; try { - FileOutputStream fileStream = new FileOutputStream(new File(filePath)); - ObjectOutputStream objectStream = new ObjectOutputStream(fileStream); + fileStream = new FileOutputStream(new File(filePath)); + objectStream = new ObjectOutputStream(fileStream); objectStream.writeObject(bootMitigationCounts); objectStream.flush(); - objectStream.close(); - fileStream.close(); } catch (Exception e) { Slog.i(TAG, "Could not save observers metadata to file: " + e); + return; + } finally { + IoUtils.closeQuietly(objectStream); + IoUtils.closeQuietly(fileStream); } } @@ -2233,23 +2237,32 @@ public class PackageWatchdog { void readAllObserversBootMitigationCountIfNecessary(String filePath) { File metadataFile = new File(filePath); if (metadataFile.exists()) { + FileInputStream fileStream = null; + ObjectInputStream objectStream = null; + HashMap<String, Integer> bootMitigationCounts = null; try { - FileInputStream fileStream = new FileInputStream(metadataFile); - ObjectInputStream objectStream = new ObjectInputStream(fileStream); - HashMap<String, Integer> bootMitigationCounts = + fileStream = new FileInputStream(metadataFile); + objectStream = new ObjectInputStream(fileStream); + bootMitigationCounts = (HashMap<String, Integer>) objectStream.readObject(); - objectStream.close(); - fileStream.close(); - - for (int i = 0; i < mAllObservers.size(); i++) { - final ObserverInternal observer = mAllObservers.valueAt(i); - if (bootMitigationCounts.containsKey(observer.name)) { - observer.setBootMitigationCount( - bootMitigationCounts.get(observer.name)); - } - } } catch (Exception e) { Slog.i(TAG, "Could not read observer metadata file: " + e); + return; + } finally { + IoUtils.closeQuietly(objectStream); + IoUtils.closeQuietly(fileStream); + } + + if (bootMitigationCounts == null || bootMitigationCounts.isEmpty()) { + Slog.i(TAG, "No observer in metadata file"); + return; + } + for (int i = 0; i < mAllObservers.size(); i++) { + final ObserverInternal observer = mAllObservers.valueAt(i); + if (bootMitigationCounts.containsKey(observer.name)) { + observer.setBootMitigationCount( + bootMitigationCounts.get(observer.name)); + } } } } diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java index ffae5176cebf..ac815f8aca85 100644 --- a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java +++ b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java @@ -2021,15 +2021,19 @@ public class PackageWatchdog { bootMitigationCounts.put(observer.name, observer.getBootMitigationCount()); } + FileOutputStream fileStream = null; + ObjectOutputStream objectStream = null; try { - FileOutputStream fileStream = new FileOutputStream(new File(filePath)); - ObjectOutputStream objectStream = new ObjectOutputStream(fileStream); + fileStream = new FileOutputStream(new File(filePath)); + objectStream = new ObjectOutputStream(fileStream); objectStream.writeObject(bootMitigationCounts); objectStream.flush(); - objectStream.close(); - fileStream.close(); } catch (Exception e) { Slog.i(TAG, "Could not save observers metadata to file: " + e); + return; + } finally { + IoUtils.closeQuietly(objectStream); + IoUtils.closeQuietly(fileStream); } } @@ -2180,23 +2184,32 @@ public class PackageWatchdog { void readAllObserversBootMitigationCountIfNecessary(String filePath) { File metadataFile = new File(filePath); if (metadataFile.exists()) { + FileInputStream fileStream = null; + ObjectInputStream objectStream = null; + HashMap<String, Integer> bootMitigationCounts = null; try { - FileInputStream fileStream = new FileInputStream(metadataFile); - ObjectInputStream objectStream = new ObjectInputStream(fileStream); - HashMap<String, Integer> bootMitigationCounts = + fileStream = new FileInputStream(metadataFile); + objectStream = new ObjectInputStream(fileStream); + bootMitigationCounts = (HashMap<String, Integer>) objectStream.readObject(); - objectStream.close(); - fileStream.close(); - - for (int i = 0; i < mAllObservers.size(); i++) { - final ObserverInternal observer = mAllObservers.valueAt(i); - if (bootMitigationCounts.containsKey(observer.name)) { - observer.setBootMitigationCount( - bootMitigationCounts.get(observer.name)); - } - } } catch (Exception e) { Slog.i(TAG, "Could not read observer metadata file: " + e); + return; + } finally { + IoUtils.closeQuietly(objectStream); + IoUtils.closeQuietly(fileStream); + } + + if (bootMitigationCounts == null || bootMitigationCounts.isEmpty()) { + Slog.i(TAG, "No observer in metadata file"); + return; + } + for (int i = 0; i < mAllObservers.size(); i++) { + final ObserverInternal observer = mAllObservers.valueAt(i); + if (bootMitigationCounts.containsKey(observer.name)) { + observer.setBootMitigationCount( + bootMitigationCounts.get(observer.name)); + } } } } diff --git a/packages/SettingsLib/ButtonPreference/Android.bp b/packages/SettingsLib/ButtonPreference/Android.bp index 0382829b2652..08dd27fd25ce 100644 --- a/packages/SettingsLib/ButtonPreference/Android.bp +++ b/packages/SettingsLib/ButtonPreference/Android.bp @@ -24,4 +24,8 @@ android_library { sdk_version: "system_current", min_sdk_version: "21", + apex_available: [ + "//apex_available:platform", + "com.android.healthfitness", + ], } diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java index 993555e78bea..be711accd542 100644 --- a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java +++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java @@ -247,4 +247,28 @@ public class ButtonPreference extends Preference implements GroupSectionDividerM mButton.setLayoutParams(lp); } } + + /** + * Sets the style of the button. + * + * @param type Specifies the button's type, which sets the attribute `buttonPreferenceType`. + * Possible values are: + * <ul> + * <li>0: filled</li> + * <li>1: tonal</li> + * <li>2: outline</li> + * </ul> + * @param size Specifies the button's size, which sets the attribute `buttonPreferenceSize`. + * Possible values are: + * <ul> + * <li>0: normal</li> + * <li>1: large</li> + * <li>2: extra large</li> + * </ul> + */ + public void setButtonStyle(int type, int size) { + int layoutId = ButtonStyle.getLayoutId(type, size); + setLayoutResource(layoutId); + notifyChanged(); + } } diff --git a/packages/SettingsLib/LayoutPreference/src/com/android/settingslib/widget/LayoutPreference.java b/packages/SettingsLib/LayoutPreference/src/com/android/settingslib/widget/LayoutPreference.java index 49f045f4423c..5df617c37945 100644 --- a/packages/SettingsLib/LayoutPreference/src/com/android/settingslib/widget/LayoutPreference.java +++ b/packages/SettingsLib/LayoutPreference/src/com/android/settingslib/widget/LayoutPreference.java @@ -41,7 +41,7 @@ import androidx.preference.PreferenceViewHolder; * xxxxxxx:allowDividerBelow="true" * */ -public class LayoutPreference extends Preference { +public class LayoutPreference extends Preference implements GroupSectionDividerMixin { private final View.OnClickListener mClickListener = v -> performClick(v); private boolean mAllowDividerAbove; diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java b/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java index 03a2101544be..218983a55e1b 100644 --- a/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java +++ b/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java @@ -31,7 +31,6 @@ import androidx.preference.CheckBoxPreference; import androidx.preference.PreferenceViewHolder; import com.android.settingslib.widget.preference.selector.R; -import com.android.settingslib.widget.selectorwithwidgetpreference.flags.Flags; /** * Selector preference (checkbox or radio button) with an optional additional widget. @@ -180,10 +179,8 @@ public class SelectorWithWidgetPreference extends CheckBoxPreference { : getContext().getString(R.string.settings_label)); } - if (Flags.allowSetTitleMaxLines()) { - TextView title = (TextView) holder.findViewById(android.R.id.title); - title.setMaxLines(mTitleMaxLines); - } + TextView title = (TextView) holder.findViewById(android.R.id.title); + title.setMaxLines(mTitleMaxLines); } /** @@ -244,16 +241,12 @@ public class SelectorWithWidgetPreference extends CheckBoxPreference { setLayoutResource(R.layout.preference_selector_with_widget); setIconSpaceReserved(false); - if (Flags.allowSetTitleMaxLines()) { - final TypedArray a = - context.obtainStyledAttributes( - attrs, R.styleable.SelectorWithWidgetPreference, defStyleAttr, - defStyleRes); - mTitleMaxLines = - a.getInt(R.styleable.SelectorWithWidgetPreference_titleMaxLines, - DEFAULT_MAX_LINES); - a.recycle(); - } + final TypedArray a = + context.obtainStyledAttributes( + attrs, R.styleable.SelectorWithWidgetPreference, defStyleAttr, defStyleRes); + mTitleMaxLines = + a.getInt(R.styleable.SelectorWithWidgetPreference_titleMaxLines, DEFAULT_MAX_LINES); + a.recycle(); } @VisibleForTesting(otherwise = VisibleForTesting.NONE) diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig index 1a043d5015b2..d82b58ec1358 100644 --- a/packages/SettingsLib/aconfig/settingslib.aconfig +++ b/packages/SettingsLib/aconfig/settingslib.aconfig @@ -97,6 +97,7 @@ flag { name: "settings_catalyst" namespace: "android_settings" description: "Settings catalyst project migration" + is_exported: true bug: "323791114" is_exported: true } @@ -106,6 +107,7 @@ flag { is_fixed_read_only: true namespace: "android_settings" description: "Enable WRITE_SYSTEM_PREFERENCE permission and appop" + is_exported: true bug: "375193223" is_exported: true } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index cf452314163f..c9aac91f1320 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -21,6 +21,7 @@ import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_ import android.annotation.IntDef; import android.annotation.MainThread; +import android.app.ActivityManager; import android.app.AppGlobals; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -1643,7 +1644,7 @@ public class AccessPoint implements Comparable<AccessPoint> { CharSequence appLabel = ""; ApplicationInfo appInfo = null; try { - int userId = UserHandle.getUserId(UserHandle.USER_CURRENT); + int userId = ActivityManager.getCurrentUser(); appInfo = packageManager.getApplicationInfoAsUser(packageName, 0 /* flags */, userId); } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Failed to get app info", e); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/SelectorWithWidgetPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/SelectorWithWidgetPreferenceTest.java index 2b8b3b74dab9..c939c770b63d 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/SelectorWithWidgetPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/SelectorWithWidgetPreferenceTest.java @@ -21,9 +21,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import android.app.Application; -import android.platform.test.annotations.DisableFlags; -import android.platform.test.annotations.EnableFlags; -import android.platform.test.flag.junit.SetFlagsRule; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; @@ -33,10 +30,8 @@ import androidx.preference.PreferenceViewHolder; import androidx.test.core.app.ApplicationProvider; import com.android.settingslib.widget.preference.selector.R; -import com.android.settingslib.widget.selectorwithwidgetpreference.flags.Flags; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; @@ -45,7 +40,6 @@ import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public class SelectorWithWidgetPreferenceTest { - @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private Application mContext; private SelectorWithWidgetPreference mPreference; @@ -128,26 +122,6 @@ public class SelectorWithWidgetPreferenceTest { } @Test - @DisableFlags(Flags.FLAG_ALLOW_SET_TITLE_MAX_LINES) - public void onBindViewHolder_titleMaxLinesSet_flagOff_titleMaxLinesMatchesDefault() { - final int titleMaxLines = 5; - AttributeSet attributeSet = Robolectric.buildAttributeSet() - .addAttribute(R.attr.titleMaxLines, String.valueOf(titleMaxLines)) - .build(); - mPreference = new SelectorWithWidgetPreference(mContext, attributeSet); - View view = LayoutInflater.from(mContext) - .inflate(mPreference.getLayoutResource(), null /* root */); - PreferenceViewHolder preferenceViewHolder = - PreferenceViewHolder.createInstanceForTests(view); - - mPreference.onBindViewHolder(preferenceViewHolder); - - TextView title = (TextView) preferenceViewHolder.findViewById(android.R.id.title); - assertThat(title.getMaxLines()).isEqualTo(SelectorWithWidgetPreference.DEFAULT_MAX_LINES); - } - - @Test - @EnableFlags(Flags.FLAG_ALLOW_SET_TITLE_MAX_LINES) public void onBindViewHolder_noTitleMaxLinesSet_titleMaxLinesMatchesDefault() { AttributeSet attributeSet = Robolectric.buildAttributeSet().build(); mPreference = new SelectorWithWidgetPreference(mContext, attributeSet); @@ -163,7 +137,6 @@ public class SelectorWithWidgetPreferenceTest { } @Test - @EnableFlags(Flags.FLAG_ALLOW_SET_TITLE_MAX_LINES) public void onBindViewHolder_titleMaxLinesSet_titleMaxLinesUpdated() { final int titleMaxLines = 5; AttributeSet attributeSet = Robolectric.buildAttributeSet() diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java index 91ac34ac8233..de7c450d8d39 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java @@ -148,27 +148,7 @@ public final class DeviceConfigService extends Binder { // TODO(b/364399200): use filter to skip instead? return; } - - ArrayList<String> missingFiles = new ArrayList<String>(); - for (String fileName : sAconfigTextProtoFilesOnDevice) { - File aconfigFile = new File(fileName); - if (!aconfigFile.exists()) { - missingFiles.add(fileName); - } - } - - if (missingFiles.isEmpty()) { - pw.println("\nAconfig flags:"); - for (String name : MyShellCommand.listAllAconfigFlags(iprovider)) { - pw.println(name); - } - } else { - pw.println("\nFailed to dump aconfig flags due to missing files:"); - for (String fileName : missingFiles) { - pw.println(fileName); - } - } - } + } private static HashSet<String> getAconfigFlagNamesInDeviceConfig() { HashSet<String> nameSet = new HashSet<String>(); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index 1c4def39eaa0..e01cb84f60ae 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -16,6 +16,19 @@ package com.android.providers.settings; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_DEVICE_SPECIFIC_CONFIG; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_GLOBAL; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_LOCALE; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_LOCK_SETTINGS; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_NETWORK_POLICIES; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SECURE; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SIM_SPECIFIC_SETTINGS; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SIM_SPECIFIC_SETTINGS_2; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SOFTAP_CONFIG; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SYSTEM; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_WIFI_NEW_CONFIG; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_WIFI_SETTINGS_BACKUP_DATA; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -99,22 +112,6 @@ public class SettingsBackupAgent extends BackupAgentHelper { private static final int NULL_SIZE = -1; private static final float FONT_SCALE_DEF_VALUE = 1.0f; - private static final String KEY_SYSTEM = "system"; - private static final String KEY_SECURE = "secure"; - private static final String KEY_GLOBAL = "global"; - private static final String KEY_LOCALE = "locale"; - private static final String KEY_LOCK_SETTINGS = "lock_settings"; - private static final String KEY_SOFTAP_CONFIG = "softap_config"; - private static final String KEY_NETWORK_POLICIES = "network_policies"; - private static final String KEY_WIFI_NEW_CONFIG = "wifi_new_config"; - private static final String KEY_DEVICE_SPECIFIC_CONFIG = "device_specific_config"; - private static final String KEY_SIM_SPECIFIC_SETTINGS = "sim_specific_settings"; - // Restoring sim-specific data backed up from newer Android version to Android 12 was causing a - // fatal crash. Creating a backup with a different key will prevent Android 12 versions from - // restoring this data. - private static final String KEY_SIM_SPECIFIC_SETTINGS_2 = "sim_specific_settings_2"; - private static final String KEY_WIFI_SETTINGS_BACKUP_DATA = "wifi_settings_backup_data"; - // Versioning of the state file. Increment this version // number any time the set of state items is altered. private static final int STATE_VERSION = 9; @@ -257,6 +254,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); if (com.android.server.backup.Flags.enableMetricsSettingsBackupAgents()) { mBackupRestoreEventLogger = this.getBackupRestoreEventLogger(); + mSettingsHelper.setBackupRestoreEventLogger(mBackupRestoreEventLogger); numberOfSettingsPerKey = new HashMap<>(); areAgentMetricsEnabled = true; } @@ -412,9 +410,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { mSettingsHelper .setLocaleData( localeData, - size, - mBackupRestoreEventLogger, - KEY_LOCALE); + size); break; case KEY_WIFI_CONFIG : @@ -552,8 +548,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { if (nBytes > buffer.length) buffer = new byte[nBytes]; in.readFully(buffer, 0, nBytes); mSettingsHelper - .setLocaleData( - buffer, nBytes, mBackupRestoreEventLogger, KEY_LOCALE); + .setLocaleData(buffer, nBytes); // Restore older backups performing the necessary migrations. if (version < FULL_BACKUP_ADDED_WIFI_NEW) { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupRestoreKeys.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupRestoreKeys.java new file mode 100644 index 000000000000..745c2fb5409d --- /dev/null +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupRestoreKeys.java @@ -0,0 +1,61 @@ +/* + * 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.net.Uri; +import android.provider.Settings; + +/** + * Class to store the keys used for backup and restore. + */ +final class SettingsBackupRestoreKeys { + static final String KEY_UNKNOWN = "unknown"; + static final String KEY_SYSTEM = "system"; + static final String KEY_SECURE = "secure"; + static final String KEY_GLOBAL = "global"; + static final String KEY_LOCALE = "locale"; + static final String KEY_LOCK_SETTINGS = "lock_settings"; + static final String KEY_SOFTAP_CONFIG = "softap_config"; + static final String KEY_NETWORK_POLICIES = "network_policies"; + static final String KEY_WIFI_NEW_CONFIG = "wifi_new_config"; + static final String KEY_DEVICE_SPECIFIC_CONFIG = "device_specific_config"; + static final String KEY_SIM_SPECIFIC_SETTINGS = "sim_specific_settings"; + // Restoring sim-specific data backed up from newer Android version to Android 12 was causing a + // fatal crash. Creating a backup with a different key will prevent Android 12 versions from + // restoring this data. + static final String KEY_SIM_SPECIFIC_SETTINGS_2 = "sim_specific_settings_2"; + static final String KEY_WIFI_SETTINGS_BACKUP_DATA = "wifi_settings_backup_data"; + + /** + * Returns the key corresponding to the given URI. + * + * @param uri The URI of the setting's destination. + * @return The key corresponding to the given URI, or KEY_UNKNOWN if the URI is not recognized. + */ + static String getKeyFromUri(Uri uri) { + if (uri.equals(Settings.Secure.CONTENT_URI)) { + return KEY_SECURE; + } else if (uri.equals(Settings.System.CONTENT_URI)) { + return KEY_SYSTEM; + } else if (uri.equals(Settings.Global.CONTENT_URI)) { + return KEY_GLOBAL; + } else { + return KEY_UNKNOWN; + } + } + +}
\ No newline at end of file diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java index 924c151a99a0..ab8d739feb43 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java @@ -17,6 +17,7 @@ package com.android.providers.settings; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.IActivityManager; import android.app.backup.BackupRestoreEventLogger; @@ -84,6 +85,7 @@ public class SettingsHelper { private Context mContext; private AudioManager mAudioManager; private TelephonyManager mTelephonyManager; + @Nullable private BackupRestoreEventLogger mBackupRestoreEventLogger; /** * A few settings elements are special in that a restore of those values needs to @@ -741,11 +743,8 @@ public class SettingsHelper { * * @param data the comma separated BCP-47 language tags in bytes. * @param size the size of the data in bytes. - * @param backupRestoreEventLogger the logger to log the restore event. - * @param dataType the data type of the setting for logging purposes. */ - /* package */ void setLocaleData( - byte[] data, int size, BackupRestoreEventLogger backupRestoreEventLogger, String dataType) { + /* package */ void setLocaleData(byte[] data, int size) { final Configuration conf = mContext.getResources().getConfiguration(); // Replace "_" with "-" to deal with older backups. @@ -772,15 +771,15 @@ public class SettingsHelper { am.updatePersistentConfigurationWithAttribution(config, mContext.getOpPackageName(), mContext.getAttributionTag()); - if (Flags.enableMetricsSettingsBackupAgents()) { - backupRestoreEventLogger - .logItemsRestored(dataType, localeList.size()); + if (Flags.enableMetricsSettingsBackupAgents() && mBackupRestoreEventLogger != null) { + mBackupRestoreEventLogger + .logItemsRestored(SettingsBackupRestoreKeys.KEY_LOCALE, localeList.size()); } } catch (RemoteException e) { - if (Flags.enableMetricsSettingsBackupAgents()) { - backupRestoreEventLogger + if (Flags.enableMetricsSettingsBackupAgents() && mBackupRestoreEventLogger != null) { + mBackupRestoreEventLogger .logItemsRestoreFailed( - dataType, + SettingsBackupRestoreKeys.KEY_LOCALE, localeList.size(), ERROR_REMOTE_EXCEPTION_SETTING_LOCALE_DATA); } @@ -795,4 +794,13 @@ public class SettingsHelper { AudioManager am = new AudioManager(mContext); am.reloadAudioSettings(); } + + /** + * Sets the backup restore event logger. + * + * @param backupRestoreEventLogger the logger to log B&R metrics. + */ + void setBackupRestoreEventLogger(BackupRestoreEventLogger backupRestoreEventLogger) { + mBackupRestoreEventLogger = backupRestoreEventLogger; + } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index f9c64422b0db..661a09553914 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -2520,6 +2520,13 @@ class SettingsProtoDumpUtil { Settings.Secure.RTT_CALLING_MODE, SecureSettingsProto.RTT_CALLING_MODE); + final long screenoffudfpsenabledToken = p.start( + SecureSettingsProto.SCREEN_OFF_UDFPS_ENABLED); + dumpSetting(s, p, + Settings.Secure.SCREEN_OFF_UNLOCK_UDFPS_ENABLED, + SecureSettingsProto.SCREEN_OFF_UDFPS_ENABLED); + p.end(screenoffudfpsenabledToken); + final long screensaverToken = p.start(SecureSettingsProto.SCREENSAVER); dumpSetting(s, p, Settings.Secure.SCREENSAVER_ENABLED, diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 55f48e3e367f..f1f03c31f718 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -120,6 +120,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.accessibility.util.AccessibilityUtils; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; import com.android.internal.display.RefreshRateSettingsUtils; import com.android.internal.os.BackgroundThread; @@ -2914,6 +2915,14 @@ public class SettingsProvider extends ContentProvider { }; } + @VisibleForTesting + void injectServices(UserManager userManager, IPackageManager packageManager, + SystemConfigManager sysConfigManager) { + mUserManager = userManager; + mPackageManager = packageManager; + mSysConfigManager = sysConfigManager; + } + private static final class Arguments { private static final Pattern WHERE_PATTERN_WITH_PARAM_NO_BRACKETS = Pattern.compile("[\\s]*name[\\s]*=[\\s]*\\?[\\s]*"); @@ -3080,6 +3089,7 @@ public class SettingsProvider extends ContentProvider { private static final String SSAID_USER_KEY = "userkey"; + @GuardedBy("mLock") private final SparseArray<SettingsState> mSettingsStates = new SparseArray<>(); private GenerationRegistry mGenerationRegistry; @@ -3992,6 +4002,14 @@ public class SettingsProvider extends ContentProvider { } } + @VisibleForTesting + void injectSettings(SettingsState settings, int type, int userId) { + int key = makeKey(type, userId); + synchronized (mLock) { + mSettingsStates.put(key, settings); + } + } + private final class MyHandler extends Handler { private static final int MSG_NOTIFY_URI_CHANGED = 1; private static final int MSG_NOTIFY_DATA_CHANGED = 2; @@ -4023,12 +4041,21 @@ public class SettingsProvider extends ContentProvider { } } - private final class UpgradeController { + @VisibleForTesting + final class UpgradeController { private static final int SETTINGS_VERSION = 226; private final int mUserId; + private final Injector mInjector; + public UpgradeController(int userId) { + this(/* injector= */ null, userId); + } + + @VisibleForTesting + UpgradeController(Injector injector, int userId) { + mInjector = injector == null ? new Injector() : injector; mUserId = userId; } @@ -6136,8 +6163,8 @@ public class SettingsProvider extends ContentProvider { systemSettings.getSettingLocked(Settings.System.PEAK_REFRESH_RATE); final Setting minRefreshRateSetting = systemSettings.getSettingLocked(Settings.System.MIN_REFRESH_RATE); - float highestRefreshRate = RefreshRateSettingsUtils - .findHighestRefreshRateForDefaultDisplay(getContext()); + float highestRefreshRate = + mInjector.findHighestRefreshRateForDefaultDisplay(getContext()); if (!peakRefreshRateSetting.isNull()) { try { @@ -6318,6 +6345,14 @@ public class SettingsProvider extends ContentProvider { private long getBitMask(int capability) { return 1 << (capability - 1); } + + @VisibleForTesting + static class Injector { + float findHighestRefreshRateForDefaultDisplay(Context context) { + return RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay( + context); + } + } } /** diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 5cd534e62ea9..bf3afeda448e 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -107,7 +107,7 @@ import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon; * the same lock to grab the current state to write to disk. * </p> */ -final class SettingsState { +public class SettingsState { private static final boolean DEBUG = false; private static final boolean DEBUG_PERSISTENCE = false; @@ -1838,7 +1838,7 @@ final class SettingsState { } } - class Setting { + public class Setting { private String name; private String value; private String defaultValue; diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupRestoreKeysTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupRestoreKeysTest.java new file mode 100644 index 000000000000..ef537e8c7fc0 --- /dev/null +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupRestoreKeysTest.java @@ -0,0 +1,58 @@ +/* + * 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 static com.google.common.truth.Truth.assertThat; + +import android.net.Uri; +import android.provider.Settings; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link SettingsBackupRestoreKeys}. + */ +@RunWith(AndroidJUnit4.class) +public class SettingsBackupRestoreKeysTest { + + @Test + public void getKeyFromUri_secureUri_returnsSecureKey() { + assertThat(SettingsBackupRestoreKeys.getKeyFromUri(Settings.Secure.CONTENT_URI)) + .isEqualTo(SettingsBackupRestoreKeys.KEY_SECURE); + } + + @Test + public void getKeyFromUri_systemUri_returnsSystemKey() { + assertThat(SettingsBackupRestoreKeys.getKeyFromUri(Settings.System.CONTENT_URI)) + .isEqualTo(SettingsBackupRestoreKeys.KEY_SYSTEM); + } + + @Test + public void getKeyFromUri_globalUri_returnsGlobalKey() { + assertThat(SettingsBackupRestoreKeys.getKeyFromUri(Settings.Global.CONTENT_URI)) + .isEqualTo(SettingsBackupRestoreKeys.KEY_GLOBAL); + } + + @Test + public void getKeyFromUri_unknownUri_returnsUnknownKey() { + assertThat(SettingsBackupRestoreKeys.getKeyFromUri(Uri.parse("content://unknown"))) + .isEqualTo(SettingsBackupRestoreKeys.KEY_UNKNOWN); + } +}
\ No newline at end of file diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java index 9cce43160b52..119b2870b622 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java @@ -16,7 +16,7 @@ package com.android.providers.settings; -import static android.provider.Settings.Secure.ACCESSIBILITY_ENABLED; +import static android.provider.Settings.Secure.CONTENT_CAPTURE_ENABLED; import static android.provider.Settings.Secure.SYNC_PARENT_SOUNDS; import static android.provider.Settings.System.RINGTONE; @@ -67,7 +67,7 @@ public class SettingsProviderMultiUsersTest { private static final String SPACE_SYSTEM = "system"; private static final String SPACE_SECURE = "secure"; - private static final String CLONE_TO_MANAGED_PROFILE_SETTING = ACCESSIBILITY_ENABLED; + private static final String CLONE_TO_MANAGED_PROFILE_SETTING = CONTENT_CAPTURE_ENABLED; private static final String CLONE_FROM_PARENT_SETTINGS = RINGTONE; private static final String SYNC_FROM_PARENT_SETTINGS = SYNC_PARENT_SOUNDS; diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/UpgradeControllerTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/UpgradeControllerTest.java new file mode 100644 index 000000000000..26ff376f828e --- /dev/null +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/UpgradeControllerTest.java @@ -0,0 +1,175 @@ +/* + * 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.providers.settings; + +import static android.provider.Settings.System.MIN_REFRESH_RATE; +import static android.provider.Settings.System.PEAK_REFRESH_RATE; + +import static com.android.providers.settings.SettingsProvider.SETTINGS_TYPE_GLOBAL; +import static com.android.providers.settings.SettingsProvider.SETTINGS_TYPE_SECURE; +import static com.android.providers.settings.SettingsProvider.SETTINGS_TYPE_SYSTEM; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +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.content.Context; +import android.content.ContextWrapper; +import android.content.pm.IPackageManager; +import android.os.Looper; +import android.os.SystemConfigManager; +import android.os.UserHandle; +import android.os.UserManager; + +import androidx.test.core.app.ApplicationProvider; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class UpgradeControllerTest { + private static final int USER_ID = UserHandle.USER_SYSTEM; + private static final float HIGHEST_REFRESH_RATE = 130f; + + private final Context mContext = + spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); + private final SettingsProvider.SettingsRegistry.UpgradeController.Injector mInjector = + new SettingsProvider.SettingsRegistry.UpgradeController.Injector() { + @Override + float findHighestRefreshRateForDefaultDisplay(Context context) { + return HIGHEST_REFRESH_RATE; + } + }; + private final SettingsProvider mSettingsProvider = new SettingsProvider() { + @Override + public boolean onCreate() { + return true; + } + }; + private final SettingsProvider.SettingsRegistry mSettingsRegistry = + mSettingsProvider.new SettingsRegistry(Looper.getMainLooper()); + private final SettingsProvider.SettingsRegistry.UpgradeController mUpgradeController = + mSettingsRegistry.new UpgradeController(mInjector, USER_ID); + + @Mock + private UserManager mUserManager; + + @Mock + private IPackageManager mPackageManager; + + @Mock + private SystemConfigManager mSysConfigManager; + + @Mock + private SettingsState mSystemSettings; + + @Mock + private SettingsState mSecureSettings; + + @Mock + private SettingsState mGlobalSettings; + + @Mock + private SettingsState.Setting mMockSetting; + + @Mock + private SettingsState.Setting mPeakRefreshRateSetting; + + @Mock + private SettingsState.Setting mMinRefreshRateSetting; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mSettingsProvider.attachInfoForTesting(mContext, /* info= */ null); + mSettingsProvider.injectServices(mUserManager, mPackageManager, mSysConfigManager); + when(mSystemSettings.getSettingLocked(any())).thenReturn(mMockSetting); + when(mSecureSettings.getSettingLocked(any())).thenReturn(mMockSetting); + when(mGlobalSettings.getSettingLocked(any())).thenReturn(mMockSetting); + when(mMockSetting.isNull()).thenReturn(true); + when(mMockSetting.getValue()).thenReturn("0"); + + when(mSystemSettings.getSettingLocked(PEAK_REFRESH_RATE)) + .thenReturn(mPeakRefreshRateSetting); + when(mSystemSettings.getSettingLocked(MIN_REFRESH_RATE)) + .thenReturn(mMinRefreshRateSetting); + + mSettingsRegistry.injectSettings(mSystemSettings, SETTINGS_TYPE_SYSTEM, USER_ID); + mSettingsRegistry.injectSettings(mSecureSettings, SETTINGS_TYPE_SECURE, USER_ID); + mSettingsRegistry.injectSettings(mGlobalSettings, SETTINGS_TYPE_GLOBAL, USER_ID); + + // Lowest version so that all upgrades are run + when(mSecureSettings.getVersionLocked()).thenReturn(118); + } + + @Test + public void testUpgrade_refreshRateSettings_defaultValues() { + when(mPeakRefreshRateSetting.isNull()).thenReturn(true); + when(mMinRefreshRateSetting.isNull()).thenReturn(true); + + mUpgradeController.upgradeIfNeededLocked(); + + // Should remain unchanged + verify(mSystemSettings, never()).insertSettingLocked(eq(PEAK_REFRESH_RATE), + /* value= */ any(), /* tag= */ any(), /* makeDefault= */ anyBoolean(), + /* packageName= */ any()); + verify(mSystemSettings, never()).insertSettingLocked(eq(MIN_REFRESH_RATE), + /* value= */ any(), /* tag= */ any(), /* makeDefault= */ anyBoolean(), + /* packageName= */ any()); + } + + @Test + public void testUpgrade_refreshRateSettings_enabled() { + when(mPeakRefreshRateSetting.isNull()).thenReturn(false); + when(mMinRefreshRateSetting.isNull()).thenReturn(false); + when(mPeakRefreshRateSetting.getValue()).thenReturn(String.valueOf(HIGHEST_REFRESH_RATE)); + when(mMinRefreshRateSetting.getValue()).thenReturn(String.valueOf(HIGHEST_REFRESH_RATE)); + + mUpgradeController.upgradeIfNeededLocked(); + + // Highest refresh rate gets converted to infinity + verify(mSystemSettings).insertSettingLocked(eq(PEAK_REFRESH_RATE), + eq(String.valueOf(Float.POSITIVE_INFINITY)), /* tag= */ any(), + /* makeDefault= */ anyBoolean(), /* packageName= */ any()); + verify(mSystemSettings).insertSettingLocked(eq(MIN_REFRESH_RATE), + eq(String.valueOf(Float.POSITIVE_INFINITY)), /* tag= */ any(), + /* makeDefault= */ anyBoolean(), /* packageName= */ any()); + } + + @Test + public void testUpgrade_refreshRateSettings_disabled() { + when(mPeakRefreshRateSetting.isNull()).thenReturn(false); + when(mMinRefreshRateSetting.isNull()).thenReturn(false); + when(mPeakRefreshRateSetting.getValue()).thenReturn("70f"); + when(mMinRefreshRateSetting.getValue()).thenReturn("70f"); + + mUpgradeController.upgradeIfNeededLocked(); + + // Should remain unchanged + verify(mSystemSettings, never()).insertSettingLocked(eq(PEAK_REFRESH_RATE), + /* value= */ any(), /* tag= */ any(), /* makeDefault= */ anyBoolean(), + /* packageName= */ any()); + verify(mSystemSettings, never()).insertSettingLocked(eq(MIN_REFRESH_RATE), + /* value= */ any(), /* tag= */ any(), /* makeDefault= */ anyBoolean(), + /* packageName= */ any()); + } +} diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 3d250fd82473..0600fb3abd6f 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -85,6 +85,7 @@ filegroup { filegroup { name: "SystemUI-tests-broken-robofiles-run", srcs: [ + "tests/src/**/systemui/dreams/touch/CommunalTouchHandlerTest.java", "tests/src/**/systemui/shade/NotificationShadeWindowViewControllerTest.kt", "tests/src/**/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt", "tests/src/**/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt", @@ -418,6 +419,9 @@ android_library { "androidx.slice_slice-view", ], manifest: "AndroidManifest-res.xml", + flags_packages: [ + "com_android_systemui_flags", + ], } android_library { diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 1b1c91de1e56..5ff2d1b07347 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -496,6 +496,13 @@ flag { } flag { + name: "status_bar_popup_chips" + namespace: "systemui" + description: "Show rich ongoing processes as chips in the status bar" + bug: "372964148" +} + +flag { name: "promote_notifications_automatically" namespace: "systemui" description: "Flag to automatically turn certain notifications into promoted notifications so " @@ -1229,6 +1236,21 @@ flag { } flag { + name: "glanceable_hub_v2_resources" + namespace: "systemui" + description: "Read only flag for rolling out glanceable hub v2 resource values" + bug: "375689917" + is_fixed_read_only: true +} + +flag { + name: "glanceable_hub_back_action" + namespace: "systemui" + description: "Support back action from glanceable hub" + bug: "382771533" +} + +flag { name: "dream_overlay_updated_font" namespace: "systemui" description: "Flag to enable updated font settings for dream overlay" diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java index ca2b9578f2be..7d27a562f536 100644 --- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java +++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java @@ -195,7 +195,10 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub { // Create the origin leash and add to the transition root leash. mOriginLeash = new SurfaceControl.Builder().setName("OriginTransition-origin-leash").build(); - mStartTransaction + + // Create temporary transaction to build + final SurfaceControl.Transaction tmpTransaction = new SurfaceControl.Transaction(); + tmpTransaction .reparent(mOriginLeash, rootLeash) .show(mOriginLeash) .setCornerRadius(mOriginLeash, windowRadius) @@ -208,14 +211,14 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub { int mode = change.getMode(); SurfaceControl leash = change.getLeash(); // Reparent leash to the transition root. - mStartTransaction.reparent(leash, rootLeash); + tmpTransaction.reparent(leash, rootLeash); if (TransitionUtil.isOpeningMode(mode)) { openingSurfaces.add(change.getLeash()); // For opening surfaces, ending bounds are base bound. Apply corner radius if // it's full screen. Rect bounds = change.getEndAbsBounds(); if (displayBounds.equals(bounds)) { - mStartTransaction + tmpTransaction .setCornerRadius(leash, windowRadius) .setWindowCrop(leash, bounds.width(), bounds.height()); } @@ -226,28 +229,53 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub { // it's full screen. Rect bounds = change.getStartAbsBounds(); if (displayBounds.equals(bounds)) { - mStartTransaction + tmpTransaction .setCornerRadius(leash, windowRadius) .setWindowCrop(leash, bounds.width(), bounds.height()); } } } + if (openingSurfaces.isEmpty() && closingSurfaces.isEmpty()) { + logD("prepareUIs: no opening/closing surfaces available, nothing to prepare."); + return false; + } + // Set relative order: // ---- App1 ---- // ---- origin ---- // ---- App2 ---- + if (mIsEntry) { - mStartTransaction - .setRelativeLayer(mOriginLeash, closingSurfaces.get(0), 1) - .setRelativeLayer( - openingSurfaces.get(openingSurfaces.size() - 1), mOriginLeash, 1); + if (!closingSurfaces.isEmpty()) { + tmpTransaction + .setRelativeLayer(mOriginLeash, closingSurfaces.get(0), 1); + } else { + logW("Missing closing surface is entry transition"); + } + if (!openingSurfaces.isEmpty()) { + tmpTransaction + .setRelativeLayer( + openingSurfaces.get(openingSurfaces.size() - 1), mOriginLeash, 1); + } else { + logW("Missing opening surface is entry transition"); + } + } else { - mStartTransaction - .setRelativeLayer(mOriginLeash, openingSurfaces.get(0), 1) - .setRelativeLayer( - closingSurfaces.get(closingSurfaces.size() - 1), mOriginLeash, 1); + if (!openingSurfaces.isEmpty()) { + tmpTransaction + .setRelativeLayer(mOriginLeash, openingSurfaces.get(0), 1); + } else { + logW("Missing opening surface is exit transition"); + } + if (!closingSurfaces.isEmpty()) { + tmpTransaction.setRelativeLayer( + closingSurfaces.get(closingSurfaces.size() - 1), mOriginLeash, 1); + } else { + logW("Missing closing surface is exit transition"); + } } + mStartTransaction.merge(tmpTransaction); // Attach origin UIComponent to origin leash. mOriginTransaction = mOrigin.newTransaction(); @@ -300,6 +328,7 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub { } private void cancel() { + logD("cancel()"); if (mAnimator != null) { mAnimator.cancel(); } @@ -311,6 +340,10 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub { } } + private static void logW(String msg) { + Log.w(TAG, msg); + } + private static void logE(String msg) { Log.e(TAG, msg); } 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 41a00f5237f7..b0c7ac09551a 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt @@ -17,7 +17,6 @@ package com.android.systemui.animation import android.app.ActivityManager -import android.app.ActivityOptions import android.app.ActivityTaskManager import android.app.PendingIntent import android.app.TaskInfo @@ -292,7 +291,7 @@ constructor( ?: throw IllegalStateException( "ActivityTransitionAnimator.callback must be set before using this animator" ) - val runner = createRunner(controller) + val runner = createEphemeralRunner(controller) val runnerDelegate = runner.delegate val hideKeyguardWithAnimation = callback.isOnKeyguard() && !showOverLockscreen @@ -416,7 +415,7 @@ constructor( var cleanUpRunnable: Runnable? = null val returnRunner = - createRunner( + createEphemeralRunner( object : DelegateTransitionAnimatorController(launchController) { override val isLaunching = false @@ -458,11 +457,7 @@ constructor( "${launchController.transitionCookie}_returnTransition", ) - transitionRegister?.register( - filter, - transition, - includeTakeover = longLivedReturnAnimationsEnabled(), - ) + transitionRegister?.register(filter, transition, includeTakeover = false) cleanUpRunnable = Runnable { transitionRegister?.unregister(transition) } } @@ -476,12 +471,14 @@ constructor( listeners.remove(listener) } - /** Create a new animation [Runner] controlled by [controller]. */ + /** + * Create a new animation [Runner] controlled by [controller]. + * + * This method must only be used for ephemeral (launch or return) transitions. Otherwise, use + * [createLongLivedRunner]. + */ @VisibleForTesting - @JvmOverloads - fun createRunner(controller: Controller, longLived: Boolean = false): Runner { - if (longLived) assertLongLivedReturnAnimations() - + fun createEphemeralRunner(controller: Controller): Runner { // Make sure we use the modified timings when animating a dialog into an app. val transitionAnimator = if (controller.isDialogLaunch) { @@ -490,7 +487,22 @@ constructor( transitionAnimator } - return Runner(controller, callback!!, transitionAnimator, lifecycleListener, longLived) + return Runner(controller, callback!!, transitionAnimator, lifecycleListener) + } + + /** + * Create a new animation [Runner] controlled by the [Controller] that [controllerFactory] can + * create based on [forLaunch]. + * + * This method must only be used for long-lived registrations. Otherwise, use + * [createEphemeralRunner]. + */ + @VisibleForTesting + fun createLongLivedRunner(controllerFactory: ControllerFactory, forLaunch: Boolean): Runner { + assertLongLivedReturnAnimations() + return Runner(callback!!, transitionAnimator, lifecycleListener) { + controllerFactory.createController(forLaunch) + } } interface PendingIntentStarter { @@ -537,6 +549,23 @@ constructor( } /** + * A factory used to create instances of [Controller] linked to a specific cookie [cookie] and + * [component]. + */ + abstract class ControllerFactory( + val cookie: TransitionCookie, + val component: ComponentName?, + val launchCujType: Int? = null, + val returnCujType: Int? = null, + ) { + /** + * Creates a [Controller] for launching or returning from the activity linked to [cookie] + * and [component]. + */ + abstract fun createController(forLaunch: Boolean): Controller + } + + /** * A controller that takes care of applying the animation to an expanding view. * * Note that all callbacks (onXXX methods) are all called on the main thread. @@ -656,13 +685,13 @@ constructor( } /** - * Registers [controller] as a long-lived transition handler for launch and return animations. + * Registers [controllerFactory] as a long-lived transition handler for launch and return + * animations. * - * The [controller] will only be used for transitions matching the [TransitionCookie] defined - * within it, or the [ComponentName] if the cookie matching fails. Both fields are mandatory for - * this registration. + * The [Controller]s created by [controllerFactory] will only be used for transitions matching + * the [cookie], or the [ComponentName] defined within it if the cookie matching fails. */ - fun register(controller: Controller) { + fun register(cookie: TransitionCookie, controllerFactory: ControllerFactory) { assertLongLivedReturnAnimations() if (transitionRegister == null) { @@ -672,13 +701,8 @@ constructor( ) } - val cookie = - controller.transitionCookie - ?: throw IllegalStateException( - "A cookie must be defined in order to use long-lived animations" - ) val component = - controller.component + controllerFactory.component ?: throw IllegalStateException( "A component must be defined in order to use long-lived animations" ) @@ -699,15 +723,11 @@ constructor( } val launchRemoteTransition = RemoteTransition( - OriginTransition(createRunner(controller, longLived = true)), + OriginTransition(createLongLivedRunner(controllerFactory, forLaunch = true)), "${cookie}_launchTransition", ) transitionRegister.register(launchFilter, launchRemoteTransition, includeTakeover = true) - val returnController = - object : Controller by controller { - override val isLaunching: Boolean = false - } // Cross-task close transitions should not use this animation, so we only register it for // when the opening window is Launcher. val returnFilter = @@ -727,7 +747,7 @@ constructor( } val returnRemoteTransition = RemoteTransition( - OriginTransition(createRunner(returnController, longLived = true)), + OriginTransition(createLongLivedRunner(controllerFactory, forLaunch = false)), "${cookie}_returnTransition", ) transitionRegister.register(returnFilter, returnRemoteTransition, includeTakeover = true) @@ -918,32 +938,61 @@ constructor( } @VisibleForTesting - inner class Runner( + inner class Runner + private constructor( /** * This can hold a reference to a view, so it needs to be cleaned up and can't be held on to - * forever when ![longLived]. + * forever. In case of a long-lived [Runner], this must be null and [controllerFactory] must + * be defined instead. */ private var controller: Controller?, + /** + * Reusable factory to generate single-use controllers. In case of an ephemeral [Runner], + * this must be null and [controller] must be defined instead. + */ + private val controllerFactory: (() -> Controller)?, private val callback: Callback, /** The animator to use to animate the window transition. */ private val transitionAnimator: TransitionAnimator, /** Listener for animation lifecycle events. */ - private val listener: Listener? = null, - /** - * Whether the internal should be kept around after execution for later usage. IMPORTANT: - * should always be false if this [Runner] is to be used directly with [ActivityOptions] - * (i.e. for ephemeral launches), or the controller will leak its view. - */ - private val longLived: Boolean = false, + private val listener: Listener?, ) : IRemoteAnimationRunner.Stub() { + constructor( + controller: Controller, + callback: Callback, + transitionAnimator: TransitionAnimator, + listener: Listener? = null, + ) : this( + controller = controller, + controllerFactory = null, + callback = callback, + transitionAnimator = transitionAnimator, + listener = listener, + ) + + constructor( + callback: Callback, + transitionAnimator: TransitionAnimator, + listener: Listener? = null, + controllerFactory: () -> Controller, + ) : this( + controller = null, + controllerFactory = controllerFactory, + callback = callback, + transitionAnimator = transitionAnimator, + listener = listener, + ) + // This is being passed across IPC boundaries and cycles (through PendingIntentRecords, // etc.) are possible. So we need to make sure we drop any references that might // transitively cause leaks when we're done with animation. @VisibleForTesting var delegate: AnimationDelegate? init { + assert((controller != null).xor(controllerFactory != null)) + delegate = null - if (!longLived) { + if (controller != null) { // Ephemeral launches bundle the runner with the launch request (instead of being // registered ahead of time for later use). This means that there could be a timeout // between creation and invocation, so the delegate needs to exist from the @@ -1021,17 +1070,21 @@ constructor( @AnyThread private fun maybeSetUp() { - if (!longLived || delegate != null) return + if (controllerFactory == null || delegate != null) return createDelegate() } @AnyThread private fun createDelegate() { - if (controller == null) return + var controller = controller + val factory = controllerFactory + if (controller == null && factory == null) return + + controller = controller ?: factory!!.invoke() delegate = AnimationDelegate( mainExecutor, - controller!!, + controller, callback, DelegatingAnimationCompletionListener(listener, this::dispose), transitionAnimator, @@ -1041,13 +1094,12 @@ constructor( @AnyThread fun dispose() { - // Drop references to animation controller once we're done with the animation - // to avoid leaking. + // Drop references to animation controller once we're done with the animation to avoid + // leaking in case of ephemeral launches. When long-lived, [controllerFactory] will + // still be around to create new controllers. mainExecutor.execute { delegate = null - // When long lived, the same Runner can be used more than once. In this case we need - // to keep the controller around so we can rebuild the delegate on demand. - if (!longLived) controller = null + controller = null } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt index a17a1d46554f..0a0003ee9a8a 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt @@ -19,17 +19,20 @@ package com.android.systemui.communal.ui.compose import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import com.android.compose.animation.scene.SceneScope +import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection import com.android.systemui.communal.ui.compose.section.CommunalPopupSection @@ -39,8 +42,11 @@ import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection import com.android.systemui.keyguard.ui.composable.section.LockSection +import com.android.systemui.res.R import com.android.systemui.statusbar.phone.SystemUIDialogFactory import javax.inject.Inject +import kotlin.math.min +import kotlin.math.roundToInt /** Renders the content of the glanceable hub. */ class CommunalContent @@ -48,6 +54,7 @@ class CommunalContent constructor( private val viewModel: CommunalViewModel, private val interactionHandler: SmartspaceInteractionHandler, + private val communalSettingsInteractor: CommunalSettingsInteractor, private val dialogFactory: SystemUIDialogFactory, private val lockSection: LockSection, private val bottomAreaSection: BottomAreaSection, @@ -77,11 +84,20 @@ constructor( sceneScope = this@Content, ) } - with(lockSection) { - LockIcon( - overrideColor = MaterialTheme.colorScheme.onPrimaryContainer, + if (communalSettingsInteractor.isV2FlagEnabled()) { + Icon( + painter = painterResource(id = R.drawable.ic_lock), + contentDescription = null, + tint = MaterialTheme.colorScheme.onPrimaryContainer, modifier = Modifier.element(Communal.Elements.LockIcon), ) + } else { + with(lockSection) { + LockIcon( + overrideColor = MaterialTheme.colorScheme.onPrimaryContainer, + modifier = Modifier.element(Communal.Elements.LockIcon), + ) + } } with(bottomAreaSection) { IndicationArea( @@ -98,14 +114,42 @@ constructor( val noMinConstraints = constraints.copy(minWidth = 0, minHeight = 0) - val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints) + val lockIconPlaceable = + if (communalSettingsInteractor.isV2FlagEnabled()) { + val lockIconSizeInt = lockIconSize.roundToPx() + lockIconMeasurable.measure( + Constraints.fixed(width = lockIconSizeInt, height = lockIconSizeInt) + ) + } else { + lockIconMeasurable.measure(noMinConstraints) + } val lockIconBounds = - IntRect( - left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left], - top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top], - right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right], - bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom], - ) + if (communalSettingsInteractor.isV2FlagEnabled()) { + val lockIconDistanceFromBottom = + min( + (constraints.maxHeight * lockIconPercentDistanceFromBottom) + .roundToInt(), + lockIconMinDistanceFromBottom.roundToPx(), + ) + val x = constraints.maxWidth / 2 - lockIconPlaceable.width / 2 + val y = + constraints.maxHeight - + lockIconDistanceFromBottom - + lockIconPlaceable.height + IntRect( + left = x, + top = y, + right = x + lockIconPlaceable.width, + bottom = y + lockIconPlaceable.height, + ) + } else { + IntRect( + left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left], + top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top], + right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right], + bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom], + ) + } val bottomAreaPlaceable = bottomAreaMeasurable.measure(noMinConstraints) @@ -129,12 +173,17 @@ constructor( val bottomAreaTop = constraints.maxHeight - bottomAreaPlaceable.height bottomAreaPlaceable.place(x = 0, y = bottomAreaTop) + + val screensaverButtonPaddingInt = screensaverButtonPadding.roundToPx() screensaverButtonPlaceable?.place( x = constraints.maxWidth - screensaverButtonSizeInt - - Dimensions.ItemSpacing.roundToPx(), - y = lockIconBounds.top, + screensaverButtonPaddingInt, + y = + constraints.maxHeight - + screensaverButtonSizeInt - + screensaverButtonPaddingInt, ) } } @@ -142,6 +191,12 @@ constructor( } companion object { - val screensaverButtonSize: Dp = 64.dp + private val screensaverButtonSize: Dp = 64.dp + private val screensaverButtonPadding: Dp = 24.dp + // TODO(b/382739998): Remove these hardcoded values once lock icon size and bottom area + // position are sorted. + private val lockIconSize: Dp = 54.dp + private val lockIconPercentDistanceFromBottom = 0.1f + private val lockIconMinDistanceFromBottom = 70.dp } } 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 5dbedc7045e4..bf3360f0ea14 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 @@ -931,7 +931,9 @@ private fun BoxScope.CommunalHubLazyGrid( Modifier.requiredSize(dpSize) .thenIf(!isItemDragging) { Modifier.animateItem( - placementSpec = spring(stiffness = Spring.StiffnessMediumLow) + placementSpec = spring(stiffness = Spring.StiffnessMediumLow), + // See b/376495198 - not supported with AndroidView + fadeOutSpec = null, ) } .thenIf(isItemDragging) { Modifier.zIndex(1f) }, @@ -980,11 +982,14 @@ private fun BoxScope.CommunalHubLazyGrid( size = size, selected = false, modifier = - Modifier.requiredSize(dpSize).animateItem().thenIf( - communalResponsiveGrid() - ) { - Modifier.graphicsLayer { alpha = itemAlpha?.value ?: 1f } - }, + Modifier.requiredSize(dpSize) + .animateItem( + // See b/376495198 - not supported with AndroidView + fadeOutSpec = null + ) + .thenIf(communalResponsiveGrid()) { + Modifier.graphicsLayer { alpha = itemAlpha?.value ?: 1f } + }, index = index, contentListState = contentListState, interactionHandler = interactionHandler, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt index 5c7ca97474b7..0344ab8e0196 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt @@ -38,7 +38,6 @@ import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.thenIf import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn import com.android.systemui.keyguard.ui.composable.modifier.burnInAware @@ -90,14 +89,10 @@ constructor( ) { init { - if (!MigrateClocksToBlueprint.isEnabled) { - throw IllegalStateException("this requires MigrateClocksToBlueprint.isEnabled") - } // This scene container section moves the NSSL to the SharedNotificationContainer. // This also requires that SharedNotificationContainer gets moved to the // SceneWindowRootView by the SceneWindowRootViewBinder. Prior to Scene Container, - // but when the KeyguardShadeMigrationNssl flag is enabled, NSSL is moved into this - // container by the NotificationStackScrollLayoutSection. + // NSSL is moved into this container by the NotificationStackScrollLayoutSection. // Ensure stackScrollLayout is a child of sharedNotificationContainer. if (stackScrollLayout.parent != sharedNotificationContainer) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt index db33e7c628d7..79cf24b9c547 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt @@ -35,9 +35,8 @@ import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.MutableSceneTransitionLayoutState -import com.android.compose.animation.scene.SceneScope -import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.modifiers.thenIf import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene @@ -61,7 +60,7 @@ constructor( private val clockInteractor: KeyguardClockInteractor, ) { @Composable - fun SceneScope.DefaultClockLayout( + fun ContentScope.DefaultClockLayout( smartSpacePaddingTop: (Resources) -> Int, isShadeLayoutWide: Boolean, modifier: Modifier = Modifier, @@ -95,7 +94,7 @@ constructor( } Column(modifier) { - SceneTransitionLayout(state) { + NestedSceneTransitionLayout(state, Modifier) { scene(splitShadeLargeClockScene) { LargeClockWithSmartSpace( smartSpacePaddingTop = smartSpacePaddingTop, @@ -134,7 +133,7 @@ constructor( } @Composable - private fun SceneScope.SmallClockWithSmartSpace( + private fun ContentScope.SmallClockWithSmartSpace( smartSpacePaddingTop: (Resources) -> Int, modifier: Modifier = Modifier, ) { @@ -159,7 +158,7 @@ constructor( } @Composable - private fun SceneScope.LargeClockWithSmartSpace( + private fun ContentScope.LargeClockWithSmartSpace( smartSpacePaddingTop: (Resources) -> Int, shouldOffSetClockToOneHalf: Boolean = false, ) { @@ -200,7 +199,7 @@ constructor( } @Composable - private fun SceneScope.WeatherLargeClockWithSmartSpace( + private fun ContentScope.WeatherLargeClockWithSmartSpace( smartSpacePaddingTop: (Resources) -> Int, modifier: Modifier = Modifier, ) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt index 2af5ffaee7ed..5790c4af0d77 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt @@ -19,6 +19,7 @@ package com.android.systemui.notifications.ui.composable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.layoutId import com.android.compose.animation.scene.ContentScope @@ -84,7 +85,11 @@ constructor( viewModel.notificationsPlaceholderViewModelFactory.create() } - OverlayShade(modifier = modifier, onScrimClicked = viewModel::onScrimClicked) { + OverlayShade( + panelAlignment = Alignment.TopStart, + modifier = modifier, + onScrimClicked = viewModel::onScrimClicked, + ) { Column { if (viewModel.showHeader) { val burnIn = rememberBurnIn(clockInteractor) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt index b1a19456ab7d..f6c5f588aa95 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt @@ -99,7 +99,11 @@ constructor( val viewModel = rememberViewModel("QuickSettingsShadeOverlay") { contentViewModelFactory.create() } - OverlayShade(modifier = modifier, onScrimClicked = viewModel::onScrimClicked) { + OverlayShade( + panelAlignment = Alignment.TopEnd, + modifier = modifier, + onScrimClicked = viewModel::onScrimClicked, + ) { Column { ExpandedShadeHeader( viewModelFactory = viewModel.shadeHeaderViewModelFactory, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt index 55fafd5cfeca..8907aec7fd48 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.gestures.Orientation import com.android.compose.animation.scene.ProgressConverter import com.android.compose.animation.scene.TransitionKey import com.android.compose.animation.scene.transitions -import com.android.systemui.bouncer.ui.composable.Bouncer import com.android.systemui.notifications.ui.composable.Notifications import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes @@ -110,17 +109,13 @@ val SceneContainerTransitions = transitions { // Overlay transitions - // TODO(b/376659778): Remove this transition once nested STLs are supported. - from(Scenes.Gone, to = Overlays.NotificationsShade) { - toNotificationsShadeTransition(translateClock = true) - } to(Overlays.NotificationsShade) { toNotificationsShadeTransition() } to(Overlays.QuickSettingsShade) { toQuickSettingsShadeTransition() } from(Overlays.NotificationsShade, to = Overlays.QuickSettingsShade) { notificationsShadeToQuickSettingsShadeTransition() } from(Scenes.Gone, to = Overlays.NotificationsShade, key = SlightlyFasterShadeCollapse) { - toNotificationsShadeTransition(translateClock = true, durationScale = 0.9) + toNotificationsShadeTransition(durationScale = 0.9) } from(Scenes.Gone, to = Overlays.QuickSettingsShade, key = SlightlyFasterShadeCollapse) { toQuickSettingsShadeTransition(durationScale = 0.9) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt index 6bdb36331709..3d62151baf2f 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt @@ -29,10 +29,7 @@ import com.android.systemui.shade.ui.composable.OverlayShade import com.android.systemui.shade.ui.composable.Shade import kotlin.time.Duration.Companion.milliseconds -fun TransitionBuilder.toNotificationsShadeTransition( - translateClock: Boolean = false, - durationScale: Double = 1.0, -) { +fun TransitionBuilder.toNotificationsShadeTransition(durationScale: Double = 1.0) { spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt()) swipeSpec = spring( @@ -45,11 +42,6 @@ fun TransitionBuilder.toNotificationsShadeTransition( elevateInContent = Overlays.NotificationsShade, ) scaleSize(OverlayShade.Elements.Panel, height = 0f) - // TODO(b/376659778): This is a temporary hack to have a shared element transition with the - // lockscreen clock. Remove once nested STLs are supported. - if (!translateClock) { - translate(ClockElementKeys.smallClockElementKey) - } // Avoid translating the status bar with the shade panel. translate(NotificationsShade.Elements.StatusBar) // Slide in the shade panel from the top edge. diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt index 8a5c96da5ac6..cfbe6671db02 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt @@ -20,6 +20,9 @@ package com.android.systemui.shade.ui.composable import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.rememberScrollableState +import androidx.compose.foundation.gestures.scrollable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.PaddingValues @@ -41,30 +44,51 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp +import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.LowestZIndexContentPicker -import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.effect.rememberOffsetOverscrollEffect import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.res.R /** Renders a lightweight shade UI container, as an overlay. */ @Composable -fun SceneScope.OverlayShade( +fun ContentScope.OverlayShade( + panelAlignment: Alignment, onScrimClicked: () -> Unit, modifier: Modifier = Modifier, content: @Composable () -> Unit, ) { - Box(modifier) { + // TODO(b/384653288) This should be removed when b/378470603 is done. + val idleEffect = rememberOffsetOverscrollEffect(Orientation.Vertical) + Box( + modifier + .overscroll(idleEffect) + .nestedScroll( + remember { + object : NestedScrollConnection { + override suspend fun onPreFling(available: Velocity): Velocity { + return available + } + } + } + ) + .scrollable(rememberScrollableState { 0f }, Orientation.Vertical, idleEffect) + ) { Scrim(onClicked = onScrimClicked) - Box(modifier = Modifier.fillMaxSize().panelPadding(), contentAlignment = Alignment.TopEnd) { + Box(modifier = Modifier.fillMaxSize().panelPadding(), contentAlignment = panelAlignment) { Panel( modifier = Modifier.element(OverlayShade.Elements.Panel) @@ -77,7 +101,7 @@ fun SceneScope.OverlayShade( } @Composable -private fun SceneScope.Scrim(onClicked: () -> Unit, modifier: Modifier = Modifier) { +private fun ContentScope.Scrim(onClicked: () -> Unit, modifier: Modifier = Modifier) { Spacer( modifier = modifier @@ -89,7 +113,7 @@ private fun SceneScope.Scrim(onClicked: () -> Unit, modifier: Modifier = Modifie } @Composable -private fun SceneScope.Panel(modifier: Modifier = Modifier, content: @Composable () -> Unit) { +private fun ContentScope.Panel(modifier: Modifier = Modifier, content: @Composable () -> Unit) { Box(modifier = modifier.clip(OverlayShade.Shapes.RoundedCornerPanel)) { Spacer( modifier = @@ -180,7 +204,6 @@ object OverlayShade { object Dimensions { val PanelCornerRadius = 46.dp - val OverscrollLimit = 32.dp } object Shapes { 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 fa5f72bc0997..1480db9de701 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 @@ -37,12 +37,14 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Slider import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.semantics.CustomAccessibilityAction @@ -66,6 +68,10 @@ import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderState +import kotlin.math.round +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map @Composable fun VolumeSlider( @@ -196,9 +202,17 @@ private fun LegacyVolumeSlider( ) } } - - // Perform haptics due to UI composition - hapticsViewModel?.onValueChange(value) + var lastDiscreteStep by remember { mutableFloatStateOf(round(value)) } + LaunchedEffect(value) { + snapshotFlow { value } + .map { round(it) } + .filter { it != lastDiscreteStep } + .distinctUntilChanged() + .collect { discreteStep -> + lastDiscreteStep = discreteStep + hapticsViewModel?.onValueChange(discreteStep) + } + } PlatformSlider( modifier = diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index 801a2d6170cc..b76656d78cc4 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -71,7 +71,6 @@ constructor( } var hasCustomPositionUpdatedAnimation: Boolean = false - var migratedClocks: Boolean = false private val time = Calendar.getInstance() @@ -228,11 +227,7 @@ constructor( override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { logger.d("onMeasure") - if ( - migratedClocks && - !isSingleLineInternal && - MeasureSpec.getMode(heightMeasureSpec) == EXACTLY - ) { + if (!isSingleLineInternal && MeasureSpec.getMode(heightMeasureSpec) == EXACTLY) { // Call straight into TextView.setTextSize to avoid setting lastUnconstrainedTextSize val size = min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F) super.setTextSize(COMPLEX_UNIT_PX, size) @@ -248,7 +243,7 @@ constructor( } } - if (migratedClocks && hasCustomPositionUpdatedAnimation) { + if (hasCustomPositionUpdatedAnimation) { // Expand width to avoid clock being clipped during stepping animation val targetWidth = measuredWidth + MeasureSpec.getSize(widthMeasureSpec) / 2 @@ -582,12 +577,10 @@ constructor( } override fun onRtlPropertiesChanged(layoutDirection: Int) { - if (migratedClocks) { - if (layoutDirection == LAYOUT_DIRECTION_RTL) { - textAlignment = TEXT_ALIGNMENT_TEXT_END - } else { - textAlignment = TEXT_ALIGNMENT_TEXT_START - } + if (layoutDirection == LAYOUT_DIRECTION_RTL) { + textAlignment = TEXT_ALIGNMENT_TEXT_END + } else { + textAlignment = TEXT_ALIGNMENT_TEXT_START } super.onRtlPropertiesChanged(layoutDirection) } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt index ad9eba841c86..74d595ce65e6 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt @@ -20,7 +20,6 @@ import android.graphics.Rect import android.icu.text.NumberFormat import android.util.TypedValue import android.view.LayoutInflater -import android.view.View import android.widget.FrameLayout import androidx.annotation.VisibleForTesting import com.android.systemui.customization.R @@ -55,7 +54,6 @@ class DefaultClockController( private val layoutInflater: LayoutInflater, private val resources: Resources, private val settings: ClockSettings?, - private val migratedClocks: Boolean = false, messageBuffers: ClockMessageBuffers? = null, ) : ClockController { override val smallClock: DefaultClockFaceController @@ -67,7 +65,6 @@ class DefaultClockController( private val burmeseLineSpacing = resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale_burmese) private val defaultLineSpacing = resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale) - protected var onSecondaryDisplay: Boolean = false override val events: DefaultClockEvents override val config: ClockConfig by lazy { @@ -175,10 +172,7 @@ class DefaultClockController( recomputePadding(targetRegion) } - override fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) { - this@DefaultClockController.onSecondaryDisplay = onSecondaryDisplay - recomputePadding(null) - } + override fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) {} } open fun recomputePadding(targetRegion: Rect?) {} @@ -197,32 +191,11 @@ class DefaultClockController( override val config = ClockFaceConfig(hasCustomPositionUpdatedAnimation = true) init { - view.migratedClocks = migratedClocks view.hasCustomPositionUpdatedAnimation = true animations = LargeClockAnimations(view, 0f, 0f) } - override fun recomputePadding(targetRegion: Rect?) { - if (migratedClocks) { - return - } - // We center the view within the targetRegion instead of within the parent - // view by computing the difference and adding that to the padding. - val lp = view.getLayoutParams() as FrameLayout.LayoutParams - lp.topMargin = - if (onSecondaryDisplay) { - // On the secondary display we don't want any additional top/bottom margin. - 0 - } else { - val parent = view.parent - val yDiff = - if (targetRegion != null && parent is View && parent.isLaidOut()) - targetRegion.centerY() - parent.height / 2f - else 0f - (-0.5f * view.bottom + yDiff).toInt() - } - view.setLayoutParams(lp) - } + override fun recomputePadding(targetRegion: Rect?) {} /** See documentation at [AnimatableClockView.offsetGlyphsForStepClockAnimation]. */ fun offsetGlyphsForStepClockAnimation(fromLeft: Int, direction: Int, fraction: Float) { diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt index e8987257bb47..c73e1c33f88a 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt @@ -47,7 +47,6 @@ class DefaultClockProvider( val ctx: Context, val layoutInflater: LayoutInflater, val resources: Resources, - private val migratedClocks: Boolean = false, private val isClockReactiveVariantsEnabled: Boolean = false, ) : ClockProvider { private var messageBuffers: ClockMessageBuffers? = null @@ -83,14 +82,7 @@ class DefaultClockProvider( FLEX_DESIGN, ) } else { - DefaultClockController( - ctx, - layoutInflater, - resources, - settings, - migratedClocks, - messageBuffers, - ) + DefaultClockController(ctx, layoutInflater, resources, settings, messageBuffers) } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt index 21d41ae744a7..4a47f1bc12bf 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt @@ -140,8 +140,8 @@ class FlexClockFaceController( } /** - * targetRegion passed to all customized clock applies counter translationY of - * KeyguardStatusView and keyguard_large_clock_top_margin from default clock + * targetRegion passed to all customized clock applies counter translationY of Keyguard and + * keyguard_large_clock_top_margin from default clock */ override fun onTargetRegionChanged(targetRegion: Rect?) { // When a clock needs to be aligned with screen, like weather clock diff --git a/packages/SystemUI/docs/clock-plugins.md b/packages/SystemUI/docs/clock-plugins.md index fee82dfcf2e3..813038ee81ec 100644 --- a/packages/SystemUI/docs/clock-plugins.md +++ b/packages/SystemUI/docs/clock-plugins.md @@ -43,12 +43,6 @@ present in the source tree, although it will likely be removed in a later patch. SystemUI event dispatchers to the clock controllers. It maintains a set of event listeners, but otherwise attempts to do as little work as possible. It does maintain some state where necessary. -[KeyguardClockSwitchController](../src/com/android/keyguard/KeyguardClockSwitchController.java) is -the primary controller for the [KeyguardClockSwitch](../src/com/android/keyguard/KeyguardClockSwitch.java), -which serves as the view parent within SystemUI. Together they ensure the correct clock (either -large or small) is shown, handle animation between clock sizes, and control some sizing/layout -parameters for the clocks. - ### Creating a custom clock In order to create a custom clock, a partner must: - Write an implementation of ClockProviderPlugin and the subinterfaces relevant to your use-case. diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt index 85bdf9264467..cea1e9600741 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt @@ -163,6 +163,16 @@ class KeyguardDisplayManagerTest : SysuiTestCase() { } @Test + fun testShow_rearDisplayOuterDefaultActive_occluded() { + displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay) + + whenever(deviceStateHelper.isRearDisplayOuterDefaultActive(secondaryDisplay)) + .thenReturn(true) + whenever(keyguardStateController.isOccluded).thenReturn(true) + verify(presentationFactory, never()).create(eq(secondaryDisplay)) + } + + @Test fun testShow_presentationCreated() { displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt index fa5af510fec1..77e386963129 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt @@ -127,6 +127,7 @@ class ShadeTouchHandlerTest(flags: FlagsParameterization) : SysuiTestCase() { @Test @DisableFlags( Flags.FLAG_COMMUNAL_HUB, + Flags.FLAG_GLANCEABLE_HUB_V2, Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX, Flags.FLAG_SCENE_CONTAINER, ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt index 41cc6ee182cf..271cd3a4f202 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.back.domain.interactor +import android.platform.test.annotations.EnableFlags import android.platform.test.annotations.RequiresFlagsDisabled import android.platform.test.annotations.RequiresFlagsEnabled import android.platform.test.flag.junit.DeviceFlagsValueProvider @@ -31,6 +32,7 @@ import androidx.test.filters.SmallTest import com.android.internal.statusbar.IStatusBarService import com.android.systemui.Flags import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.domain.interactor.CommunalBackActionInteractor import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope @@ -93,6 +95,7 @@ class BackActionInteractorTest : SysuiTestCase() { @Mock private lateinit var onBackInvokedDispatcher: WindowOnBackInvokedDispatcher @Mock private lateinit var iStatusBarService: IStatusBarService @Mock private lateinit var headsUpManager: HeadsUpManager + @Mock private lateinit var communalBackActionInteractor: CommunalBackActionInteractor private val keyguardRepository = FakeKeyguardRepository() private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy { @@ -117,6 +120,7 @@ class BackActionInteractorTest : SysuiTestCase() { windowRootViewVisibilityInteractor, shadeBackActionInteractor, qsController, + communalBackActionInteractor, ) } @@ -306,6 +310,19 @@ class BackActionInteractorTest : SysuiTestCase() { verify(shadeBackActionInteractor).onBackProgressed(0.4f) } + @Test + @EnableFlags(Flags.FLAG_GLANCEABLE_HUB_BACK_ACTION) + fun onBackAction_communalCanBeDismissed_communalBackActionInteractorCalled() { + backActionInteractor.start() + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true) + powerInteractor.setAwakeForTest() + val callback = getBackInvokedCallback() + whenever(communalBackActionInteractor.canBeDismissed()).thenReturn(true) + callback.onBackInvoked() + + verify(communalBackActionInteractor).onBackPressed() + } + private fun getBackInvokedCallback(): OnBackInvokedCallback { testScope.runCurrent() val captor = argumentCaptor<OnBackInvokedCallback>() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt index 08f139c6a3af..9c9d5adcfcc9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt @@ -51,7 +51,7 @@ class BiometricPromptRequestTest : SysuiTestCase() { title = title, subtitle = subtitle, description = description, - contentView = contentView + contentView = contentView, ), BiometricUserInfo(USER_ID), BiometricOperationInfo(OPERATION_ID), @@ -101,9 +101,7 @@ class BiometricPromptRequestTest : SysuiTestCase() { val fpPros = fingerprintSensorPropertiesInternal().first() val request = BiometricPromptRequest.Biometric( - promptInfo( - logoBitmap = logoBitmap, - ), + promptInfo(logoBitmap = logoBitmap), BiometricUserInfo(USER_ID), BiometricOperationInfo(OPERATION_ID), BiometricModalities(fingerprintProperties = fpPros), @@ -162,7 +160,7 @@ class BiometricPromptRequestTest : SysuiTestCase() { BiometricUserInfo(USER_ID), BiometricOperationInfo(OPERATION_ID), stealth, - ) + ), ) for (request in toCheck) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalMetricsStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalMetricsStartableTest.kt index 370adee44a42..03bf79bc7a38 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalMetricsStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalMetricsStartableTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.communal import android.app.StatsManager import android.app.StatsManager.StatsPullAtomCallback import android.content.pm.UserInfo +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.util.StatsEvent import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -32,6 +33,7 @@ import com.android.systemui.communal.shared.log.CommunalMetricsLogger import com.android.systemui.concurrency.fakeExecutor import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope import com.android.systemui.settings.fakeUserTracker import com.android.systemui.shared.system.SysUiStatsLog @@ -75,10 +77,7 @@ class CommunalMetricsStartableTest : SysuiTestCase() { // Set up an existing user, which is required for widgets to show val userInfos = listOf(UserInfo(0, "main", UserInfo.FLAG_MAIN)) userRepository.setUserInfos(userInfos) - userTracker.set( - userInfos = userInfos, - selectedUserIndex = 0, - ) + userTracker.set(userInfos = userInfos, selectedUserIndex = 0) underTest = CommunalMetricsStartable( @@ -90,14 +89,16 @@ class CommunalMetricsStartableTest : SysuiTestCase() { ) } + @DisableFlags(Flags.FLAG_GLANCEABLE_HUB_V2) @Test - fun start_communalFlagDisabled_doNotSetPullAtomCallback() { - kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false) + fun start_communalFlagDisabled_doNotSetPullAtomCallback() = + kosmos.runTest { + fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false) - underTest.start() + underTest.start() - verify(statsManager, never()).setPullAtomCallback(anyInt(), anyOrNull(), any(), any()) - } + verify(statsManager, never()).setPullAtomCallback(anyInt(), anyOrNull(), any(), any()) + } @Test fun onPullAtom_atomTagDoesNotMatch_pullSkip() { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt index b66727e492cf..038ea9ccaaa9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt @@ -26,8 +26,8 @@ import android.content.res.mainResources import android.os.UserManager.USER_TYPE_PROFILE_MANAGED import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.FlagsParameterization import android.provider.Settings -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2 @@ -35,10 +35,13 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.communal.data.model.DisabledReason import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl.Companion.GLANCEABLE_HUB_BACKGROUND_SETTING +import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled import com.android.systemui.communal.shared.model.CommunalBackgroundType import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.android.systemui.util.mockito.nullable @@ -51,15 +54,21 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.eq +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @SmallTest -@RunWith(AndroidJUnit4::class) -class CommunalSettingsRepositoryImplTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiTestCase() { private val kosmos = testKosmos().apply { mainResources = mContext.orCreateTestableResources.resources } private val testScope = kosmos.testScope private lateinit var underTest: CommunalSettingsRepository + init { + mSetFlagsRule.setFlagsParameterization(flags!!) + } + @Before fun setUp() { kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) @@ -164,17 +173,17 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() { assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_INVALID_USER) } - @EnableFlags(FLAG_COMMUNAL_HUB) + @EnableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2) @Test fun classicFlagIsDisabled() = - testScope.runTest { - kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false) + kosmos.runTest { + setCommunalV2Enabled(false) val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER)) assertThat(enabledState?.enabled).isFalse() assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_FLAG) } - @DisableFlags(FLAG_COMMUNAL_HUB) + @DisableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2) @Test fun communalHubFlagIsDisabled() = testScope.runTest { @@ -295,6 +304,34 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() { } } + @Test + fun screensaverDisabledByUser() = + testScope.runTest { + val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER)) + + kosmos.fakeSettings.putIntForUser( + Settings.Secure.SCREENSAVER_ENABLED, + 0, + PRIMARY_USER.id, + ) + + assertThat(enabledState).isFalse() + } + + @Test + fun screensaverEnabledByUser() = + testScope.runTest { + val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER)) + + kosmos.fakeSettings.putIntForUser( + Settings.Secure.SCREENSAVER_ENABLED, + 1, + PRIMARY_USER.id, + ) + + assertThat(enabledState).isTrue() + } + private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) { whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id))) .thenReturn(disabledFlags) @@ -310,5 +347,11 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() { val SECONDARY_USER = UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0) val WORK_PROFILE = UserInfo(10, "work", /* iconPath= */ "", /* flags= */ 0, USER_TYPE_PROFILE_MANAGED) + + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf(FLAG_GLANCEABLE_HUB_V2) + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorTest.kt new file mode 100644 index 000000000000..c365f1cb3872 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorTest.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.domain.interactor + +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_COMMUNAL_HUB +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.data.repository.communalSceneRepository +import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.runCurrent +import com.android.systemui.kosmos.runTest +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +class CommunalBackActionInteractorTest : SysuiTestCase() { + private val kosmos = testKosmos() + + private var Kosmos.underTest by Fixture { communalBackActionInteractor } + + @Test + @EnableFlags(FLAG_COMMUNAL_HUB) + fun communalShowing_canBeDismissed() = + kosmos.runTest { + setCommunalAvailable(true) + assertThat(underTest.canBeDismissed()).isEqualTo(false) + communalInteractor.changeScene(CommunalScenes.Communal, "test") + runCurrent() + assertThat(underTest.canBeDismissed()).isEqualTo(true) + } + + @Test + @EnableFlags(FLAG_COMMUNAL_HUB) + fun onBackPressed_invokesSceneChange() = + kosmos.runTest { + underTest.onBackPressed() + runCurrent() + assertThat(communalSceneRepository.currentScene.value).isEqualTo(CommunalScenes.Blank) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index b9e646fee98f..7ae0577bd289 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -35,6 +35,7 @@ import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.Flags.FLAG_COMMUNAL_RESPONSIVE_GRID import com.android.systemui.Flags.FLAG_COMMUNAL_WIDGET_RESIZING +import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2 import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.communal.data.model.CommunalSmartspaceTimer @@ -232,7 +233,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun isCommunalAvailable_communalDisabled_false() = testScope.runTest { - mSetFlagsRule.disableFlags(FLAG_COMMUNAL_HUB) + mSetFlagsRule.disableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2) val isAvailable by collectLastValue(underTest.isCommunalAvailable) assertThat(isAvailable).isFalse() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt index 0bfcd242828d..8a9c42d9b64e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt @@ -130,19 +130,6 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { } @Test - fun tutorialState_startedAndCommunalSceneShowing_stateWillNotUpdate() = - testScope.runTest { - val tutorialSettingState by - collectLastValue(communalTutorialRepository.tutorialSettingState) - - communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED) - - goToCommunal() - - assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_STARTED) - } - - @Test fun tutorialState_completedAndCommunalSceneShowing_stateWillNotUpdate() = testScope.runTest { val tutorialSettingState by diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt index 88206850eb60..b78080885b0a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.communal.ui.viewmodel import android.platform.test.annotations.EnableFlags +import android.provider.Settings import android.service.dream.dreamManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -29,12 +30,16 @@ import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn +import com.android.systemui.plugins.activityStarter import com.android.systemui.statusbar.policy.batteryController import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.fakeUserRepository +import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mockito.verify import org.mockito.kotlin.any import org.mockito.kotlin.whenever @@ -56,10 +61,9 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() { } @Test - fun shouldShowDreamButtonOnHub_trueWhenCanDream() = + fun shouldShowDreamButtonOnHub_trueWhenPluggedIn() = with(kosmos) { runTest { - whenever(dreamManager.canStartDreaming(any())).thenReturn(true) whenever(batteryController.isPluggedIn()).thenReturn(true) val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub) @@ -68,11 +72,10 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() { } @Test - fun shouldShowDreamButtonOnHub_falseWhenCannotDream() = + fun shouldShowDreamButtonOnHub_falseWhenNotPluggedIn() = with(kosmos) { runTest { - whenever(dreamManager.canStartDreaming(any())).thenReturn(false) - whenever(batteryController.isPluggedIn()).thenReturn(true) + whenever(batteryController.isPluggedIn()).thenReturn(false) val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub) assertThat(shouldShowButton).isFalse() @@ -80,25 +83,40 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() { } @Test - fun shouldShowDreamButtonOnHub_falseWhenNotPluggedIn() = + fun onShowDreamButtonTap_dreamsEnabled_startsDream() = with(kosmos) { runTest { - whenever(dreamManager.canStartDreaming(any())).thenReturn(true) - whenever(batteryController.isPluggedIn()).thenReturn(false) + val currentUser = fakeUserRepository.asMainUser() + kosmos.fakeSettings.putIntForUser( + Settings.Secure.SCREENSAVER_ENABLED, + 1, + currentUser.id, + ) + runCurrent() - val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub) - assertThat(shouldShowButton).isFalse() + underTest.onShowDreamButtonTap() + runCurrent() + + verify(dreamManager).startDream() } } @Test - fun onShowDreamButtonTap_startsDream() = + fun onShowDreamButtonTap_dreamsDisabled_startsActivity() = with(kosmos) { runTest { + val currentUser = fakeUserRepository.asMainUser() + kosmos.fakeSettings.putIntForUser( + Settings.Secure.SCREENSAVER_ENABLED, + 0, + currentUser.id, + ) + runCurrent() + underTest.onShowDreamButtonTap() runCurrent() - verify(dreamManager).startDream() + verify(activityStarter).postStartActivityDismissingKeyguard(any(), anyInt()) } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/ControlActionCoordinatorImplTest.kt index 928514657257..c8661cf59051 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/ControlActionCoordinatorImplTest.kt @@ -16,9 +16,9 @@ package com.android.systemui.controls.ui +import android.view.HapticFeedbackConstants import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import android.view.HapticFeedbackConstants import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.controls.ControlsMetricsLogger @@ -29,6 +29,7 @@ import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.concurrency.DelayableExecutor import com.android.wm.shell.taskview.TaskViewFactory +import java.util.Optional import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -45,31 +46,20 @@ import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations -import java.util.Optional @SmallTest @RunWith(AndroidJUnit4::class) class ControlActionCoordinatorImplTest : SysuiTestCase() { - @Mock - private lateinit var vibratorHelper: VibratorHelper - @Mock - private lateinit var keyguardStateController: KeyguardStateController - @Mock - private lateinit var bgExecutor: DelayableExecutor - @Mock - private lateinit var uiExecutor: DelayableExecutor - @Mock - private lateinit var activityStarter: ActivityStarter - @Mock - private lateinit var broadcastSender: BroadcastSender - @Mock - private lateinit var taskViewFactory: Optional<TaskViewFactory> - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private lateinit var cvh: ControlViewHolder - @Mock - private lateinit var metricsLogger: ControlsMetricsLogger - @Mock - private lateinit var controlsSettingsDialogManager: ControlsSettingsDialogManager + @Mock private lateinit var vibratorHelper: VibratorHelper + @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock private lateinit var bgExecutor: DelayableExecutor + @Mock private lateinit var uiExecutor: DelayableExecutor + @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var broadcastSender: BroadcastSender + @Mock private lateinit var taskViewFactory: Optional<TaskViewFactory> + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var cvh: ControlViewHolder + @Mock private lateinit var metricsLogger: ControlsMetricsLogger + @Mock private lateinit var controlsSettingsDialogManager: ControlsSettingsDialogManager companion object { fun <T> any(): T = Mockito.any<T>() @@ -89,18 +79,21 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() { controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true) controlsSettingsRepository.setCanShowControlsInLockscreen(true) - coordinator = spy(ControlActionCoordinatorImpl( - mContext, - bgExecutor, - uiExecutor, - activityStarter, - broadcastSender, - keyguardStateController, - taskViewFactory, - metricsLogger, - vibratorHelper, - controlsSettingsRepository, - )) + coordinator = + spy( + ControlActionCoordinatorImpl( + mContext, + bgExecutor, + uiExecutor, + activityStarter, + broadcastSender, + keyguardStateController, + taskViewFactory, + metricsLogger, + vibratorHelper, + controlsSettingsRepository, + ) + ) coordinator.activityContext = mContext `when`(cvh.cws.ci.controlId).thenReturn(ID) @@ -198,19 +191,15 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() { fun drag_isEdge_performsSegmentTickHaptics() { coordinator.drag(cvh, true) - verify(vibratorHelper).performHapticFeedback( - any(), - eq(HapticFeedbackConstants.SEGMENT_TICK) - ) + verify(vibratorHelper) + .performHapticFeedback(any(), eq(HapticFeedbackConstants.SEGMENT_TICK)) } @Test fun drag_isNotEdge_performsFrequentTickHaptics() { coordinator.drag(cvh, false) - verify(vibratorHelper).performHapticFeedback( - any(), - eq(HapticFeedbackConstants.SEGMENT_FREQUENT_TICK) - ) + verify(vibratorHelper) + .performHapticFeedback(any(), eq(HapticFeedbackConstants.SEGMENT_FREQUENT_TICK)) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt index b07097d61b96..5921e9479bd9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt @@ -92,10 +92,10 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mockito import org.mockito.Mockito.clearInvocations -import org.mockito.Mockito.isNull import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.eq import org.mockito.kotlin.firstValue @@ -768,7 +768,7 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() { runCurrent() verify(mDreamOverlayCallback).onRedirectWake(true) client.onWakeRequested() - verify(mCommunalInteractor).changeScene(eq(CommunalScenes.Communal), any(), isNull()) + verify(mCommunalInteractor).changeScene(eq(CommunalScenes.Communal), any(), anyOrNull()) verify(mUiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/domain/interactor/HomeControlsComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt index c950523f7854..c950523f7854 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/domain/interactor/HomeControlsComponentInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt index 9cfd328a9484..f2a6c11b872e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt @@ -36,6 +36,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder @@ -43,6 +44,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) +@Ignore("b/384284415") class ContextualEducationRepositoryTest : SysuiTestCase() { private lateinit var underTest: UserContextualEducationRepository diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt index ee3e241b5754..56e8185ab580 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt @@ -81,7 +81,7 @@ class CameraQuickAffordanceConfigTest : SysuiTestCase() { // Then verify(cameraGestureHelper) .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE) - assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(true), result) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt index 50ac26196978..fde9b8ce6a50 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt @@ -197,7 +197,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { val dndMode = currentModes!!.single() assertThat(dndMode.isActive).isFalse() - assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result) } @Test @@ -222,7 +222,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { ) // then - assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result) assertEquals(ZEN_MODE_OFF, spyZenMode.value) assertNull(spyConditionId.value) } @@ -244,7 +244,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { val dndMode = currentModes!!.single() assertThat(dndMode.isActive).isTrue() assertThat(zenModeRepository.getModeActiveDuration(dndMode.id)).isNull() - assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result) } @Test @@ -268,7 +268,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { ) // then - assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result) assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, spyZenMode.value) assertNull(spyConditionId.value) } @@ -285,7 +285,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { val result = underTest.onTriggered(null) runCurrent() - assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result) val dndMode = currentModes!!.single() assertThat(dndMode.isActive).isTrue() assertThat(zenModeRepository.getModeActiveDuration(dndMode.id)) @@ -313,7 +313,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() { ) // then - assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result) assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, spyZenMode.value) assertEquals(conditionUri, spyConditionId.value) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceHapticViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceHapticViewModelTest.kt new file mode 100644 index 000000000000..18946f9d7e07 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceHapticViewModelTest.kt @@ -0,0 +1,116 @@ +/* + * 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.keyguard.data.quickaffordance + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.domain.interactor.keyguardQuickAffordanceHapticViewModelFactory +import com.android.systemui.keyguard.domain.interactor.keyguardQuickAffordanceInteractor +import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceHapticViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel +import com.android.systemui.kosmos.testScope +import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +class KeyguardQuickAffordanceHapticViewModelTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + private val configKey = "$slotId::home" + private val keyguardQuickAffordanceInteractor = kosmos.keyguardQuickAffordanceInteractor + private val viewModelFlow = + MutableStateFlow(KeyguardQuickAffordanceViewModel(configKey = configKey, slotId = slotId)) + + private val underTest = + kosmos.keyguardQuickAffordanceHapticViewModelFactory.create(viewModelFlow) + + @Test + fun whenLaunchingFromTriggeredResult_hapticStateIsLaunch() = + testScope.runTest { + // GIVEN that the result from triggering the affordance launched an activity or dialog + val hapticState by collectLastValue(underTest.quickAffordanceHapticState) + keyguardQuickAffordanceInteractor.setLaunchingFromTriggeredResult( + KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult(true, configKey) + ) + runCurrent() + + // THEN the haptic state indicates that a launch haptics must play + assertThat(hapticState) + .isEqualTo(KeyguardQuickAffordanceHapticViewModel.HapticState.LAUNCH) + } + + @Test + fun whenNotLaunchFromTriggeredResult_hapticStateDoesNotEmit() = + testScope.runTest { + // GIVEN that the result from triggering the affordance did not launch an activity or + // dialog + val hapticState by collectLastValue(underTest.quickAffordanceHapticState) + keyguardQuickAffordanceInteractor.setLaunchingFromTriggeredResult( + KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult(false, configKey) + ) + runCurrent() + + // THEN there is no haptic state to play any feedback + assertThat(hapticState) + .isEqualTo(KeyguardQuickAffordanceHapticViewModel.HapticState.NO_HAPTICS) + } + + @Test + fun onQuickAffordanceTogglesToActivated_hapticStateIsToggleOn() = + testScope.runTest { + // GIVEN that an affordance toggles from deactivated to activated + val hapticState by collectLastValue(underTest.quickAffordanceHapticState) + toggleQuickAffordance(on = true) + + // THEN the haptic state reflects that a toggle on haptics should play + assertThat(hapticState) + .isEqualTo(KeyguardQuickAffordanceHapticViewModel.HapticState.TOGGLE_ON) + } + + @Test + fun onQuickAffordanceTogglesToDeactivated_hapticStateIsToggleOff() = + testScope.runTest { + // GIVEN that an affordance toggles from activated to deactivated + val hapticState by collectLastValue(underTest.quickAffordanceHapticState) + toggleQuickAffordance(on = false) + + // THEN the haptic state reflects that a toggle off haptics should play + assertThat(hapticState) + .isEqualTo(KeyguardQuickAffordanceHapticViewModel.HapticState.TOGGLE_OFF) + } + + private fun TestScope.toggleQuickAffordance(on: Boolean) { + underTest.updateActivatedHistory(!on) + runCurrent() + underTest.updateActivatedHistory(on) + runCurrent() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt index b15352bfe6ab..173b4e56075c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt @@ -49,14 +49,10 @@ import org.mockito.MockitoAnnotations class MuteQuickAffordanceConfigTest : SysuiTestCase() { private lateinit var underTest: MuteQuickAffordanceConfig - @Mock - private lateinit var ringerModeTracker: RingerModeTracker - @Mock - private lateinit var audioManager: AudioManager - @Mock - private lateinit var userTracker: UserTracker - @Mock - private lateinit var userFileManager: UserFileManager + @Mock private lateinit var ringerModeTracker: RingerModeTracker + @Mock private lateinit var audioManager: AudioManager + @Mock private lateinit var userTracker: UserTracker + @Mock private lateinit var userFileManager: UserFileManager private lateinit var testDispatcher: TestDispatcher private lateinit var testScope: TestScope @@ -70,9 +66,12 @@ class MuteQuickAffordanceConfigTest : SysuiTestCase() { whenever(userTracker.userContext).thenReturn(context) whenever(userFileManager.getSharedPreferences(any(), any(), any())) - .thenReturn(context.getSharedPreferences("mutequickaffordancetest", Context.MODE_PRIVATE)) + .thenReturn( + context.getSharedPreferences("mutequickaffordancetest", Context.MODE_PRIVATE) + ) - underTest = MuteQuickAffordanceConfig( + underTest = + MuteQuickAffordanceConfig( context, userTracker, userFileManager, @@ -81,64 +80,71 @@ class MuteQuickAffordanceConfigTest : SysuiTestCase() { testScope.backgroundScope, testDispatcher, testDispatcher, - ) + ) } @Test - fun pickerState_volumeFixed_notAvailable() = testScope.runTest { - //given - whenever(audioManager.isVolumeFixed).thenReturn(true) + fun pickerState_volumeFixed_notAvailable() = + testScope.runTest { + // given + whenever(audioManager.isVolumeFixed).thenReturn(true) - //when - val result = underTest.getPickerScreenState() + // when + val result = underTest.getPickerScreenState() - //then - assertEquals(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice, result) - } + // then + assertEquals( + KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice, + result, + ) + } @Test - fun pickerState_volumeNotFixed_available() = testScope.runTest { - //given - whenever(audioManager.isVolumeFixed).thenReturn(false) + fun pickerState_volumeNotFixed_available() = + testScope.runTest { + // given + whenever(audioManager.isVolumeFixed).thenReturn(false) - //when - val result = underTest.getPickerScreenState() + // when + val result = underTest.getPickerScreenState() - //then - assertEquals(KeyguardQuickAffordanceConfig.PickerScreenState.Default(), result) - } + // then + assertEquals(KeyguardQuickAffordanceConfig.PickerScreenState.Default(), result) + } @Test - fun triggered_stateWasPreviouslyNORMAL_currentlySILENT_moveToPreviousState() = testScope.runTest { - //given - val ringerModeCapture = argumentCaptor<Int>() - whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL) - underTest.onTriggered(null) - whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_SILENT) - - //when - val result = underTest.onTriggered(null) - runCurrent() - verify(audioManager, times(2)).ringerModeInternal = ringerModeCapture.capture() - - //then - assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) - assertEquals(AudioManager.RINGER_MODE_NORMAL, ringerModeCapture.value) - } + fun triggered_stateWasPreviouslyNORMAL_currentlySILENT_moveToPreviousState() = + testScope.runTest { + // given + val ringerModeCapture = argumentCaptor<Int>() + whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL) + underTest.onTriggered(null) + whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_SILENT) + + // when + val result = underTest.onTriggered(null) + runCurrent() + verify(audioManager, times(2)).ringerModeInternal = ringerModeCapture.capture() + + // then + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result) + assertEquals(AudioManager.RINGER_MODE_NORMAL, ringerModeCapture.value) + } @Test - fun triggered_stateIsNotSILENT_moveToSILENTringer() = testScope.runTest { - //given - val ringerModeCapture = argumentCaptor<Int>() - whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL) - - //when - val result = underTest.onTriggered(null) - runCurrent() - verify(audioManager).ringerModeInternal = ringerModeCapture.capture() - - //then - assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) - assertEquals(AudioManager.RINGER_MODE_SILENT, ringerModeCapture.value) - } -}
\ No newline at end of file + fun triggered_stateIsNotSILENT_moveToSILENTringer() = + testScope.runTest { + // given + val ringerModeCapture = argumentCaptor<Int>() + whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL) + + // when + val result = underTest.onTriggered(null) + runCurrent() + verify(audioManager).ringerModeInternal = ringerModeCapture.capture() + + // then + assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result) + assertEquals(AudioManager.RINGER_MODE_SILENT, ringerModeCapture.value) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt index e9b36b8b3b57..9bdc363b3a38 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt @@ -88,9 +88,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { Icon.Loaded( drawable = ICON, contentDescription = - ContentDescription.Resource( - res = R.string.accessibility_wallet_button, - ), + ContentDescription.Resource(res = R.string.accessibility_wallet_button), ) ) } @@ -118,9 +116,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { Icon.Loaded( drawable = ICON, contentDescription = - ContentDescription.Resource( - res = R.string.accessibility_wallet_button, - ), + ContentDescription.Resource(res = R.string.accessibility_wallet_button), ) ) } @@ -163,13 +159,9 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { } assertThat(underTest.onTriggered(expandable)) - .isEqualTo(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled) + .isEqualTo(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(true)) verify(walletController) - .startQuickAccessUiIntent( - activityStarter, - animationController, - /* hasCard= */ true, - ) + .startQuickAccessUiIntent(activityStarter, animationController, /* hasCard= */ true) } @Test @@ -184,9 +176,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { @Test fun getPickerScreenState_unavailable() = testScope.runTest { - setUpState( - isWalletServiceAvailable = false, - ) + setUpState(isWalletServiceAvailable = false) assertThat(underTest.getPickerScreenState()) .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice) @@ -195,9 +185,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { @Test fun getPickerScreenState_disabledWhenTheFeatureIsNotEnabled() = testScope.runTest { - setUpState( - isWalletFeatureAvailable = false, - ) + setUpState(isWalletFeatureAvailable = false) assertThat(underTest.getPickerScreenState()) .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java) @@ -206,9 +194,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { @Test fun getPickerScreenState_disabledWhenThereIsNoCard() = testScope.runTest { - setUpState( - hasSelectedCard = false, - ) + setUpState(hasSelectedCard = false) assertThat(underTest.getPickerScreenState()) .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java) @@ -219,7 +205,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { isWalletServiceAvailable: Boolean = true, isWalletQuerySuccessful: Boolean = true, hasSelectedCard: Boolean = true, - cardType: Int = WalletCard.CARD_TYPE_UNKNOWN + cardType: Int = WalletCard.CARD_TYPE_UNKNOWN, ) { val walletClient: QuickAccessWalletClient = mock() whenever(walletClient.tileIcon).thenReturn(ICON) @@ -242,11 +228,11 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { /*cardType= */ cardType, /*cardImage= */ mock(), /*contentDescription= */ CARD_DESCRIPTION, - /*pendingIntent= */ mock() + /*pendingIntent= */ mock(), ) .build() ), - 0 + 0, ) } else { GetWalletCardsResponse(emptyList(), 0) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index 46d1ebe75899..9de0215022e9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt @@ -764,6 +764,28 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { assertThat(launchingAffordance).isFalse() } + @Test + fun onQuickAffordanceTriggered_updatesLaunchingFromTriggeredResult() = + testScope.runTest { + // WHEN selecting and triggering a quick affordance at a slot + val key = homeControls.key + val slot = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + val encodedKey = "$slot::$key" + val actionLaunched = true + homeControls.onTriggeredResult = + KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(actionLaunched) + underTest.select(slot, key) + runCurrent() + underTest.onQuickAffordanceTriggered(encodedKey, expandable = null, slot) + + // THEN the latest triggered result shows that an action launched for the same key and + // slot + val launchingFromTriggeredResult by + collectLastValue(underTest.launchingFromTriggeredResult) + assertThat(launchingFromTriggeredResult?.launched).isEqualTo(actionLaunched) + assertThat(launchingFromTriggeredResult?.configKey).isEqualTo(encodedKey) + } + companion object { private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337 private val ICON: Icon = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt index ad5eeabf83d2..26fe379f00bf 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope import com.android.systemui.scene.data.repository.sceneContainerRepository +import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -118,6 +119,50 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() { ) } + /** STL: Ls -> overlay, then settle with Idle(overlay). */ + @Test + fun transition_overlay_from_ls_scene_end_in_gone() = + testScope.runTest { + sceneTransitions.value = + ObservableTransitionState.Transition.ShowOrHideOverlay( + overlay = Overlays.NotificationsShade, + fromContent = Scenes.Lockscreen, + toContent = Overlays.NotificationsShade, + currentScene = Scenes.Lockscreen, + currentOverlays = flowOf(emptySet()), + progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + previewProgress = flowOf(0f), + isInPreviewStage = flowOf(false), + ) + + val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions) + assertTransition( + step = currentStep!!, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.RUNNING, + progress = 0f, + ) + + progress.value = 0.4f + assertTransition(step = currentStep!!, state = TransitionState.RUNNING, progress = 0.4f) + + sceneTransitions.value = + ObservableTransitionState.Idle( + Scenes.Lockscreen, + setOf(Overlays.NotificationsShade), + ) + assertTransition( + step = currentStep!!, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.FINISHED, + progress = 1f, + ) + } + /** * STL: Ls -> Gone, then settle with Idle(Ls). KTF in this scenario needs to invert the * transition LS -> UNDEFINED to UNDEFINED -> LS as there is no mechanism in KTF to @@ -259,6 +304,47 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() { ) } + /** STL: Ls with overlay, then settle with Idle(Ls). */ + @Test + fun transition_overlay_to_ls_scene_end_in_ls() = + testScope.runTest { + val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions) + sceneTransitions.value = + ObservableTransitionState.Transition.ShowOrHideOverlay( + overlay = Overlays.NotificationsShade, + fromContent = Overlays.NotificationsShade, + toContent = Scenes.Lockscreen, + currentScene = Scenes.Lockscreen, + currentOverlays = flowOf(setOf(Overlays.NotificationsShade)), + progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + previewProgress = flowOf(0f), + isInPreviewStage = flowOf(false), + ) + + assertTransition( + step = currentStep!!, + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.RUNNING, + progress = 0f, + ) + + progress.value = 0.4f + assertTransition(step = currentStep!!, state = TransitionState.RUNNING, progress = 0.4f) + + sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen) + + assertTransition( + step = currentStep!!, + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.FINISHED, + progress = 1f, + ) + } + /** STL: Gone -> Ls (AOD), will transition to AOD once */ @Test fun transition_to_ls_scene_with_changed_next_scene_is_respected_just_once() = 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 0e3b03f74c02..be504cc0f704 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 @@ -18,11 +18,8 @@ package com.android.systemui.keyguard.ui.viewmodel -import android.platform.test.annotations.DisableFlags -import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.DisableSceneContainer @@ -75,7 +72,6 @@ class AodBurnInViewModelTest : SysuiTestCase() { private val burnInFlow = MutableStateFlow(BurnInModel()) @Before - @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) @DisableSceneContainer fun setUp() { MockitoAnnotations.initMocks(this) @@ -219,50 +215,6 @@ class AodBurnInViewModelTest : SysuiTestCase() { } @Test - @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun translationAndScale_whenFullyDozing_MigrationFlagOff_staysOutOfTopInset() = - testScope.runTest { - underTest.updateBurnInParams(burnInParameters.copy(minViewY = 100, topInset = 80)) - val movement by collectLastValue(underTest.movement) - assertThat(movement?.translationX).isEqualTo(0) - - // Set to dozing (on AOD) - keyguardTransitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.GONE, - to = KeyguardState.AOD, - value = 1f, - transitionState = TransitionState.FINISHED, - ), - validateStep = false, - ) - - // Trigger a change to the burn-in model - burnInFlow.value = BurnInModel(translationX = 20, translationY = -30, scale = 0.5f) - assertThat(movement?.translationX).isEqualTo(20) - // -20 instead of -30, due to inset of 80 - assertThat(movement?.translationY).isEqualTo(-20) - assertThat(movement?.scale).isEqualTo(0.5f) - assertThat(movement?.scaleClockOnly).isEqualTo(true) - - // Set to the beginning of GONE->AOD transition - keyguardTransitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.GONE, - to = KeyguardState.AOD, - value = 0f, - transitionState = TransitionState.STARTED, - ), - validateStep = false, - ) - assertThat(movement?.translationX).isEqualTo(0) - assertThat(movement?.translationY).isEqualTo(0) - assertThat(movement?.scale).isEqualTo(1f) - assertThat(movement?.scaleClockOnly).isEqualTo(true) - } - - @Test - @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_whenFullyDozing_MigrationFlagOn_staysOutOfTopInset() = testScope.runTest { underTest.updateBurnInParams(burnInParameters.copy(minViewY = 100, topInset = 80)) @@ -334,7 +286,6 @@ class AodBurnInViewModelTest : SysuiTestCase() { @Test @DisableSceneContainer - @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_sceneContainerOff_weatherLargeClock() = testBurnInViewModelForClocks( isSmallClock = false, @@ -344,7 +295,6 @@ class AodBurnInViewModelTest : SysuiTestCase() { @Test @DisableSceneContainer - @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_sceneContainerOff_weatherSmallClock() = testBurnInViewModelForClocks( isSmallClock = true, @@ -354,7 +304,6 @@ class AodBurnInViewModelTest : SysuiTestCase() { @Test @DisableSceneContainer - @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_sceneContainerOff_nonWeatherLargeClock() = testBurnInViewModelForClocks( isSmallClock = false, @@ -364,7 +313,6 @@ class AodBurnInViewModelTest : SysuiTestCase() { @Test @DisableSceneContainer - @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_sceneContainerOff_nonWeatherSmallClock() = testBurnInViewModelForClocks( isSmallClock = true, @@ -373,7 +321,6 @@ class AodBurnInViewModelTest : SysuiTestCase() { ) @Test - @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) @EnableSceneContainer fun translationAndScale_sceneContainerOn_weatherLargeClock() = testBurnInViewModelForClocks( @@ -383,7 +330,6 @@ class AodBurnInViewModelTest : SysuiTestCase() { ) @Test - @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) @EnableSceneContainer fun translationAndScale_sceneContainerOn_weatherSmallClock() = testBurnInViewModelForClocks( @@ -393,7 +339,6 @@ class AodBurnInViewModelTest : SysuiTestCase() { ) @Test - @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) @EnableSceneContainer fun translationAndScale_sceneContainerOn_nonWeatherLargeClock() = testBurnInViewModelForClocks( @@ -403,7 +348,6 @@ class AodBurnInViewModelTest : SysuiTestCase() { ) @Test - @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) @EnableSceneContainer @Ignore("b/367659687") fun translationAndScale_sceneContainerOn_nonWeatherSmallClock() = 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 95ffc962797d..789477e38b55 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 @@ -19,12 +19,10 @@ package com.android.systemui.keyguard.ui.viewmodel -import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import android.view.View import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState -import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.communalSceneRepository import com.android.systemui.communal.shared.model.CommunalScenes @@ -73,7 +71,6 @@ import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -@EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java index 004aeb069a14..3d9fb0a9be16 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java @@ -56,6 +56,7 @@ import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; import com.android.systemui.settings.UserTracker; +import com.android.systemui.shade.domain.interactor.FakeShadeDialogContextInteractor; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.util.DeviceConfigProxyFake; import com.android.systemui.util.concurrency.FakeExecutor; @@ -84,6 +85,7 @@ public class FgsManagerControllerTest extends SysuiTestCase { FakeExecutor mMainExecutor; FakeExecutor mBackgroundExecutor; DeviceConfigProxyFake mDeviceConfigProxyFake; + FakeShadeDialogContextInteractor mFakeShadeDialogContextInteractor; @Mock IActivityManager mIActivityManager; @@ -118,6 +120,7 @@ public class FgsManagerControllerTest extends SysuiTestCase { public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); + mFakeShadeDialogContextInteractor = new FakeShadeDialogContextInteractor(mContext); mDeviceConfigProxyFake = new DeviceConfigProxyFake(); mSystemClock = new FakeSystemClock(); mMainExecutor = new FakeExecutor(mSystemClock); @@ -346,7 +349,6 @@ public class FgsManagerControllerTest extends SysuiTestCase { SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, "true", false); FgsManagerController fmc = new FgsManagerControllerImpl( - mContext, mContext.getResources(), mMainExecutor, mBackgroundExecutor, @@ -359,7 +361,8 @@ public class FgsManagerControllerTest extends SysuiTestCase { mDialogTransitionAnimator, mBroadcastDispatcher, mDumpManager, - mSystemUIDialogFactory + mSystemUIDialogFactory, + mFakeShadeDialogContextInteractor ); fmc.init(); Assert.assertTrue(fmc.getIncludesUserVisibleJobs()); @@ -374,7 +377,6 @@ public class FgsManagerControllerTest extends SysuiTestCase { SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, "false", false); fmc = new FgsManagerControllerImpl( - mContext, mContext.getResources(), mMainExecutor, mBackgroundExecutor, @@ -387,7 +389,8 @@ public class FgsManagerControllerTest extends SysuiTestCase { mDialogTransitionAnimator, mBroadcastDispatcher, mDumpManager, - mSystemUIDialogFactory + mSystemUIDialogFactory, + mFakeShadeDialogContextInteractor ); fmc.init(); Assert.assertFalse(fmc.getIncludesUserVisibleJobs()); @@ -487,7 +490,6 @@ public class FgsManagerControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(BroadcastReceiver.class); FgsManagerController result = new FgsManagerControllerImpl( - mContext, mContext.getResources(), mMainExecutor, mBackgroundExecutor, @@ -500,7 +502,8 @@ public class FgsManagerControllerTest extends SysuiTestCase { mDialogTransitionAnimator, mBroadcastDispatcher, mDumpManager, - mSystemUIDialogFactory + mSystemUIDialogFactory, + mFakeShadeDialogContextInteractor ); result.init(); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelTest.kt index a8e390c25a4d..a8e390c25a4d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt index fbbdc46a7873..0e823ccf1df5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt @@ -36,6 +36,7 @@ import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes import com.android.systemui.res.R +import com.android.systemui.shade.domain.interactor.FakeShadeDialogContextInteractor import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.DataSaverController import com.android.systemui.util.mockito.whenever @@ -96,6 +97,7 @@ class DataSaverTileTest(flags: FlagsParameterization) : SysuiTestCase() { dataSaverController, mDialogTransitionAnimator, systemUIDialogFactory, + FakeShadeDialogContextInteractor(mContext), ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java index b88861756889..5c6657b98801 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java @@ -8,6 +8,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -231,7 +232,8 @@ public class InternetAdapterTest extends SysuiTestCase { mViewHolder.onWifiClick(mWifiEntry, mock(View.class)); - verify(mSpyContext).startActivity(any()); + verify(mInternetDialogController).startActivityForDialog(any()); + verify(mSpyContext, never()).startActivity(any()); } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt index 039a1dc05c79..518a0a5a1446 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.PseudoGridView import com.android.systemui.qs.QSUserSwitcherEvent import com.android.systemui.qs.tiles.UserDetailView +import com.android.systemui.shade.domain.interactor.FakeShadeDialogContextInteractor import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq @@ -84,6 +85,7 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { mDialogTransitionAnimator, uiEventLogger, dialogFactory, + FakeShadeDialogContextInteractor(mContext), ) } @@ -91,32 +93,32 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { fun showDialog_callsDialogShow() { val launchController = mock<DialogTransitionAnimator.Controller>() `when`(launchExpandable.dialogTransitionController(any())).thenReturn(launchController) - controller.showDialog(context, launchExpandable) + controller.showDialog(launchExpandable) verify(mDialogTransitionAnimator).show(eq(dialog), eq(launchController), anyBoolean()) verify(uiEventLogger).log(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN) } @Test fun dialog_showForAllUsers() { - controller.showDialog(context, launchExpandable) + controller.showDialog(launchExpandable) verify(dialog).setShowForAllUsers(true) } @Test fun dialog_cancelOnTouchOutside() { - controller.showDialog(context, launchExpandable) + controller.showDialog(launchExpandable) verify(dialog).setCanceledOnTouchOutside(true) } @Test fun adapterAndGridLinked() { - controller.showDialog(context, launchExpandable) + controller.showDialog(launchExpandable) verify(userDetailViewAdapter).linkToViewGroup(any<PseudoGridView>()) } @Test fun doneButtonLogsCorrectly() { - controller.showDialog(context, launchExpandable) + controller.showDialog(launchExpandable) verify(dialog).setPositiveButton(anyInt(), capture(clickCaptor)) @@ -129,7 +131,7 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { fun clickSettingsButton_noFalsing_opensSettings() { `when`(falsingManager.isFalseTap(anyInt())).thenReturn(false) - controller.showDialog(context, launchExpandable) + controller.showDialog(launchExpandable) verify(dialog) .setNeutralButton(anyInt(), capture(clickCaptor), eq(false) /* dismissOnClick */) @@ -150,7 +152,7 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { fun clickSettingsButton_Falsing_notOpensSettings() { `when`(falsingManager.isFalseTap(anyInt())).thenReturn(true) - controller.showDialog(context, launchExpandable) + controller.showDialog(launchExpandable) verify(dialog) .setNeutralButton(anyInt(), capture(clickCaptor), eq(false) /* dismissOnClick */) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index e56b965d9402..3187cca6ca45 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -37,7 +37,6 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.bouncerSceneContentViewModel -import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.flags.EnableSceneContainer @@ -48,9 +47,9 @@ import com.android.systemui.keyguard.ui.viewmodel.lockscreenUserActionsViewModel import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.currentValue -import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.verifyCurrent import com.android.systemui.lifecycle.activateIn import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest @@ -77,12 +76,9 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch import kotlinx.coroutines.test.advanceTimeBy -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mockito.verify /** * Integration test cases for the Scene Framework. @@ -137,10 +133,10 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { sceneContainerViewModel.activateIn(testScope) assertWithMessage("Initial scene key mismatch!") - .that(sceneContainerViewModel.currentScene.value) + .that(currentValue(sceneContainerViewModel.currentScene)) .isEqualTo(sceneContainerConfig.initialSceneKey) assertWithMessage("Initial scene container visibility mismatch!") - .that(sceneContainerViewModel.isVisible) + .that(currentValue { sceneContainerViewModel.isVisible }) .isTrue() } @@ -337,7 +333,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { .that(bouncerActionButton) .isNotNull() kosmos.bouncerSceneContentViewModel.onActionButtonClicked(bouncerActionButton!!) - runCurrent() // TODO(b/369765704): Assert that an activity was started once we use ActivityStarter. } @@ -358,9 +353,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { .that(bouncerActionButton) .isNotNull() kosmos.bouncerSceneContentViewModel.onActionButtonClicked(bouncerActionButton!!) - runCurrent() - verify(mockTelecomManager).showInCallScreen(any()) + verifyCurrent(mockTelecomManager).showInCallScreen(any()) } @Test @@ -413,7 +407,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { * the UI must gradually transition between scenes. */ private fun Kosmos.getCurrentSceneInUi(): SceneKey { - return when (val state = transitionState.value) { + return when (val state = currentValue(transitionState)) { is ObservableTransitionState.Idle -> state.currentScene is ObservableTransitionState.Transition.ChangeScene -> state.fromScene is ObservableTransitionState.Transition.ShowOrHideOverlay -> state.currentScene @@ -436,7 +430,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { // is not an observable that can trigger a new evaluation. fakeDeviceEntryRepository.setLockscreenEnabled(enableLockscreen) fakeAuthenticationRepository.setAuthenticationMethod(authMethod) - testScope.runCurrent() } /** Emulates a phone call in progress. */ @@ -447,7 +440,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { setIsInCall(true) setCallState(TelephonyManager.CALL_STATE_OFFHOOK) } - testScope.runCurrent() } /** @@ -480,24 +472,21 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), ) - testScope.runCurrent() // Report progress of transition. - while (progressFlow.value < 1f) { + while (currentValue(progressFlow) < 1f) { progressFlow.value += 0.2f - testScope.runCurrent() } // End the transition and report the change. transitionState.value = ObservableTransitionState.Idle(to) fakeSceneDataSource.unpause(force = true) - testScope.runCurrent() assertWithMessage("Visibility mismatch after scene transition from $from to $to!") - .that(sceneContainerViewModel.isVisible) + .that(currentValue { sceneContainerViewModel.isVisible }) .isEqualTo(expectedVisible) - assertThat(sceneContainerViewModel.currentScene.value).isEqualTo(to) + assertThat(currentValue(sceneContainerViewModel.currentScene)).isEqualTo(to) bouncerSceneJob = if (to == Scenes.Bouncer) { @@ -510,7 +499,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { bouncerSceneJob?.cancel() null } - testScope.runCurrent() } /** @@ -556,13 +544,12 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { ) powerInteractor.setAwakeForTest() - testScope.runCurrent() } /** Unlocks the device by entering the correct PIN. Ends up in the Gone scene. */ private fun Kosmos.unlockDevice() { assertWithMessage("Cannot unlock a device that's already unlocked!") - .that(deviceEntryInteractor.isUnlocked.value) + .that(currentValue(deviceEntryInteractor.isUnlocked)) .isFalse() emulateUserDrivenTransition(Scenes.Bouncer) @@ -595,7 +582,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { pinBouncerViewModel.onPinButtonClicked(digit) } pinBouncerViewModel.onAuthenticateButtonClicked() - testScope.runCurrent() } /** @@ -625,26 +611,23 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { } pinBouncerViewModel.onAuthenticateButtonClicked() fakeMobileConnectionsRepository.isAnySimSecure.value = false - testScope.runCurrent() setAuthMethod(authMethodAfterSimUnlock, enableLockscreen) - testScope.runCurrent() } /** Changes device wakefulness state from asleep to awake, going through intermediary states. */ private fun Kosmos.wakeUpDevice() { - val wakefulnessModel = powerInteractor.detailedWakefulness.value + val wakefulnessModel = currentValue(powerInteractor.detailedWakefulness) assertWithMessage("Cannot wake up device as it's already awake!") .that(wakefulnessModel.isAwake()) .isFalse() powerInteractor.setAwakeForTest() - testScope.runCurrent() } /** Changes device wakefulness state from awake to asleep, going through intermediary states. */ private suspend fun Kosmos.putDeviceToSleep(waitForLock: Boolean = true) { - val wakefulnessModel = powerInteractor.detailedWakefulness.value + val wakefulnessModel = currentValue(powerInteractor.detailedWakefulness) assertWithMessage("Cannot put device to sleep as it's already asleep!") .that(wakefulnessModel.isAwake()) .isTrue() @@ -659,22 +642,18 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { ) .toLong() ) - } else { - testScope.runCurrent() } } /** Emulates the dismissal of the IME (soft keyboard). */ private fun Kosmos.dismissIme() { - (bouncerSceneContentViewModel.authMethodViewModel.value as? PasswordBouncerViewModel)?.let { - it.onImeDismissed() - testScope.runCurrent() - } + (currentValue(bouncerSceneContentViewModel.authMethodViewModel) + as? PasswordBouncerViewModel) + ?.let { it.onImeDismissed() } } private fun Kosmos.introduceLockedSim() { setAuthMethod(AuthenticationMethodModel.Sim) fakeMobileConnectionsRepository.isAnySimSecure.value = true - testScope.runCurrent() } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index 929537dcf757..0361ffe475a2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -212,18 +212,6 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { } @Test - @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun testDragDownHelperCalledWhenDraggingDown() = - testScope.runTest { - whenever(dragDownHelper.isDraggingDown).thenReturn(true) - val now = SystemClock.elapsedRealtime() - val ev = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0f, 0f, 0 /* meta */) - underTest.onTouchEvent(ev) - verify(dragDownHelper).onTouchEvent(ev) - ev.recycle() - } - - @Test fun testNoInterceptTouch() = testScope.runTest { captureInteractionEventHandler() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt index aa8b4f136683..4423426945eb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt @@ -117,7 +117,6 @@ class DefaultClockProviderTest : SysuiTestCase() { verify(mockLargeClockView).onTimeZoneChanged(notNull()) verify(mockSmallClockView).refreshTime() verify(mockLargeClockView).refreshTime() - verify(mockLargeClockView).setLayoutParams(any()) } @Test @@ -163,7 +162,6 @@ class DefaultClockProviderTest : SysuiTestCase() { clock.largeClock.events.onFontSettingChanged(200f) verify(mockLargeClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), eq(200f)) - verify(mockLargeClockView).setLayoutParams(any()) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt index 165e943a0cc0..40f13bbbf908 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt @@ -16,10 +16,12 @@ package com.android.systemui.statusbar.chips.notification.ui.viewmodel +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository @@ -71,6 +73,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_noNotifs_empty() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -81,6 +84,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_notifMissingStatusBarChipIconView_empty() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -99,6 +103,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_onePromotedNotif_statusBarIconViewMatches() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -122,6 +127,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { @Test @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_onePromotedNotif_connectedDisplaysFlagEnabled_statusBarIconMatches() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -145,6 +151,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_onePromotedNotif_colorMatches() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -175,6 +182,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_onlyForPromotedNotifs() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -208,6 +216,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { @Test @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_connectedDisplaysFlagEnabled_onlyForPromotedNotifs() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -242,6 +251,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_hasShortCriticalText_usesTextInsteadOfTime() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -272,6 +282,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_noTime_isIconOnly() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -294,6 +305,36 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) + fun chips_basicTime_hiddenIfAutomaticallyPromoted() = + kosmos.runTest { + val latest by collectLastValue(underTest.chips) + + val promotedContentBuilder = + PromotedNotificationContentModel.Builder("notif").apply { + this.time = + PromotedNotificationContentModel.When( + time = 6543L, + mode = PromotedNotificationContentModel.When.Mode.BasicTime, + ) + } + setNotifs( + listOf( + activeNotificationModel( + key = "notif", + statusBarChipIcon = mock<StatusBarIconView>(), + promotedContent = promotedContentBuilder.build(), + ) + ) + ) + + assertThat(latest).hasSize(1) + assertThat(latest!![0]) + .isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java) + } + + @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_basicTime_isShortTimeDelta() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -322,6 +363,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_countUpTime_isTimer() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -349,6 +391,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_countDownTime_isTimer() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -376,6 +419,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_noHeadsUp_showsTime() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -407,6 +451,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_hasHeadsUpByUser_onlyShowsIcon() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -442,6 +487,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_clickingChipNotifiesInteractor() = kosmos.runTest { val latest by collectLastValue(underTest.chips) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt new file mode 100644 index 000000000000..14787e169979 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.featurepods.popups.ui.viewmodel + +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.chips.notification.shared.StatusBarPopupChips +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@EnableFlags(StatusBarPopupChips.FLAG_NAME) +@RunWith(AndroidJUnit4::class) +class StatusBarPopupChipsViewModelTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val underTest = kosmos.statusBarPopupChipsViewModel + + @Test + fun popupChips_allHidden_empty() = + testScope.runTest { + val latest by collectLastValue(underTest.popupChips) + assertThat(latest).isEmpty() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt index abd0a284ae3d..3359db0a22e6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt @@ -43,7 +43,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) -class PromotedNotificationContentExtractorTest : SysuiTestCase() { +class PromotedNotificationContentExtractorImplTest : SysuiTestCase() { private val kosmos = testKosmos() private val provider = @@ -54,7 +54,7 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { @Test @DisableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) fun shouldNotExtract_bothFlagsDisabled() { - val notif = createEntry().also { provider.promotedEntries.add(it) } + val notif = createEntry() val content = extractContent(notif) assertThat(content).isNull() } @@ -63,7 +63,7 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { @EnableFlags(PromotedNotificationUi.FLAG_NAME) @DisableFlags(StatusBarNotifChips.FLAG_NAME) fun shouldExtract_promotedNotificationUiFlagEnabled() { - val entry = createEntry().also { provider.promotedEntries.add(it) } + val entry = createEntry() val content = extractContent(entry) assertThat(content).isNotNull() } @@ -72,7 +72,7 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { @EnableFlags(StatusBarNotifChips.FLAG_NAME) @DisableFlags(PromotedNotificationUi.FLAG_NAME) fun shouldExtract_statusBarNotifChipsFlagEnabled() { - val entry = createEntry().also { provider.promotedEntries.add(it) } + val entry = createEntry() val content = extractContent(entry) assertThat(content).isNotNull() } @@ -80,7 +80,7 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { @Test @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) fun shouldExtract_bothFlagsEnabled() { - val entry = createEntry().also { provider.promotedEntries.add(it) } + val entry = createEntry() val content = extractContent(entry) assertThat(content).isNotNull() } @@ -88,22 +88,19 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { @Test @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) fun shouldNotExtract_providerDidNotPromote() { - val entry = createEntry().also { provider.promotedEntries.remove(it) } + val entry = createEntry(promoted = false) val content = extractContent(entry) assertThat(content).isNull() } @Test @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) - fun extractContent_commonFields() { - val entry = - createEntry { - setSubText(TEST_SUB_TEXT) - setContentTitle(TEST_CONTENT_TITLE) - setContentText(TEST_CONTENT_TEXT) - setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) - } - .also { provider.promotedEntries.add(it) } + fun extractsContent_commonFields() { + val entry = createEntry { + setSubText(TEST_SUB_TEXT) + setContentTitle(TEST_CONTENT_TITLE) + setContentText(TEST_CONTENT_TEXT) + } val content = extractContent(entry) @@ -117,9 +114,7 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) @DisableFlags(android.app.Flags.FLAG_API_RICH_ONGOING) fun extractContent_apiFlagOff_shortCriticalTextNotExtracted() { - val entry = - createEntry { setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) } - .also { provider.promotedEntries.add(it) } + val entry = createEntry { setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) } val content = extractContent(entry) @@ -134,9 +129,7 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { android.app.Flags.FLAG_API_RICH_ONGOING, ) fun extractContent_apiFlagOn_shortCriticalTextExtracted() { - val entry = - createEntry { setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) } - .also { provider.promotedEntries.add(it) } + val entry = createEntry { setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) } val content = extractContent(entry) @@ -151,7 +144,7 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { android.app.Flags.FLAG_API_RICH_ONGOING, ) fun extractContent_noShortCriticalTextSet_textIsNull() { - val entry = createEntry {}.also { provider.promotedEntries.add(it) } + val entry = createEntry { setShortCriticalText(null) } val content = extractContent(entry) @@ -161,9 +154,8 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { @Test @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) - fun extractContent_fromBigPictureStyle() { - val entry = - createEntry { setStyle(BigPictureStyle()) }.also { provider.promotedEntries.add(it) } + fun extractsContent_fromBigPictureStyle() { + val entry = createEntry { setStyle(BigPictureStyle()) } val content = extractContent(entry) @@ -174,8 +166,7 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { @Test @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) fun extractContent_fromBigTextStyle() { - val entry = - createEntry { setStyle(BigTextStyle()) }.also { provider.promotedEntries.add(it) } + val entry = createEntry { setStyle(BigTextStyle()) } val content = extractContent(entry) @@ -187,11 +178,13 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) fun extractContent_fromCallStyle() { val hangUpIntent = - PendingIntent.getBroadcast(context, 0, Intent("hangup"), PendingIntent.FLAG_IMMUTABLE) - - val entry = - createEntry { setStyle(CallStyle.forOngoingCall(TEST_PERSON, hangUpIntent)) } - .also { provider.promotedEntries.add(it) } + PendingIntent.getBroadcast( + context, + 0, + Intent("hangup_action"), + PendingIntent.FLAG_IMMUTABLE, + ) + val entry = createEntry { setStyle(CallStyle.forOngoingCall(TEST_PERSON, hangUpIntent)) } val content = extractContent(entry) @@ -202,11 +195,9 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { @Test @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) fun extractContent_fromProgressStyle() { - val entry = - createEntry { - setStyle(ProgressStyle().addProgressSegment(Segment(100)).setProgress(75)) - } - .also { provider.promotedEntries.add(it) } + val entry = createEntry { + setStyle(ProgressStyle().addProgressSegment(Segment(100)).setProgress(75)) + } val content = extractContent(entry) @@ -220,13 +211,9 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { @Test @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) fun extractContent_fromIneligibleStyle() { - val entry = - createEntry { - setStyle( - MessagingStyle(TEST_PERSON).addMessage("message text", 0L, TEST_PERSON) - ) - } - .also { provider.promotedEntries.add(it) } + val entry = createEntry { + setStyle(MessagingStyle(TEST_PERSON).addMessage("message text", 0L, TEST_PERSON)) + } val content = extractContent(entry) @@ -239,9 +226,14 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { return underTest.extractContent(entry, recoveredBuilder) } - private fun createEntry(builderBlock: Notification.Builder.() -> Unit = {}): NotificationEntry { - val notif = Notification.Builder(context, "a").also(builderBlock).build() - return NotificationEntryBuilder().setNotification(notif).build() + private fun createEntry( + promoted: Boolean = true, + builderBlock: Notification.Builder.() -> Unit = {}, + ): NotificationEntry { + val notif = Notification.Builder(context, "channel").also(builderBlock).build() + return NotificationEntryBuilder().setNotification(notif).build().also { + provider.shouldPromoteForEntry[it] = promoted + } } companion object { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java index 6eb2764165b5..a49a66fe26b2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java @@ -16,12 +16,18 @@ package com.android.systemui.statusbar.notification.row; +import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_PUBLIC; +import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT; +import static com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType; +import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -30,14 +36,15 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.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; import static org.mockito.Mockito.when; import android.app.Notification; +import android.app.Person; import android.content.Context; +import android.graphics.drawable.Icon; import android.os.AsyncTask; import android.os.CancellationSignal; import android.os.Handler; @@ -61,12 +68,13 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips; import com.android.systemui.statusbar.notification.ConversationNotificationProcessor; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor; +import com.android.systemui.statusbar.notification.promoted.FakePromotedNotificationContentExtractor; import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi; import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; +import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction; import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor; import com.android.systemui.statusbar.policy.InflatedSmartReplyState; import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder; @@ -105,7 +113,8 @@ public class NotificationContentInflaterTest extends SysuiTestCase { @Mock private NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider; @Mock private HeadsUpStyleProvider mHeadsUpStyleProvider; @Mock private NotifLayoutInflaterFactory mNotifLayoutInflaterFactory; - @Mock private PromotedNotificationContentExtractor mPromotedNotificationContentExtractor; + private final FakePromotedNotificationContentExtractor mPromotedNotificationContentExtractor = + new FakePromotedNotificationContentExtractor(); private final SmartReplyStateInflater mSmartReplyStateInflater = new SmartReplyStateInflater() { @@ -155,8 +164,8 @@ public class NotificationContentInflaterTest extends SysuiTestCase { @Test public void testIncreasedHeadsUpBeingUsed() { - BindParams params = new BindParams(); - params.usesIncreasedHeadsUpHeight = true; + BindParams params = new BindParams(false, false, /* usesIncreasedHeadsUpHeight */ true, + REDACTION_TYPE_NONE); Notification.Builder builder = spy(mBuilder); mNotificationInflater.inflateNotificationViews( mRow.getEntry(), @@ -166,14 +175,15 @@ public class NotificationContentInflaterTest extends SysuiTestCase { FLAG_CONTENT_VIEW_ALL, builder, mContext, + mContext, mSmartReplyStateInflater); verify(builder).createHeadsUpContentView(true); } @Test public void testIncreasedHeightBeingUsed() { - BindParams params = new BindParams(); - params.usesIncreasedHeight = true; + BindParams params = new BindParams(false, /* usesIncreasedHeight */ true, false, + REDACTION_TYPE_NONE); Notification.Builder builder = spy(mBuilder); mNotificationInflater.inflateNotificationViews( mRow.getEntry(), @@ -183,6 +193,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { FLAG_CONTENT_VIEW_ALL, builder, mContext, + mContext, mSmartReplyStateInflater); verify(builder).createContentView(true); } @@ -207,7 +218,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { mRow.getEntry().getSbn().getNotification().contentView = new RemoteViews(mContext.getPackageName(), com.android.systemui.res.R.layout.status_bar); inflateAndWait(true /* expectingException */, mNotificationInflater, FLAG_CONTENT_VIEW_ALL, - mRow); + REDACTION_TYPE_NONE, mRow); assertTrue(mRow.getPrivateLayout().getChildCount() == 0); verify(mRow, times(0)).onNotificationUpdated(); } @@ -227,7 +238,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { mRow.getEntry(), mRow, FLAG_CONTENT_VIEW_ALL, - new BindParams(), + new BindParams(false, false, false, REDACTION_TYPE_NONE), false /* forceInflate */, null /* callback */); Assert.assertNull(mRow.getEntry().getRunningTask()); @@ -287,7 +298,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { mBuilder.setCustomContentView(new RemoteViews(getContext().getPackageName(), R.layout.custom_view_dark)); RemoteViews decoratedMediaView = mBuilder.createContentView(); - Assert.assertFalse("The decorated media style doesn't allow a view to be reapplied!", + assertFalse("The decorated media style doesn't allow a view to be reapplied!", NotificationContentInflater.canReapplyRemoteView(mediaView, decoratedMediaView)); } @@ -385,7 +396,8 @@ public class NotificationContentInflaterTest extends SysuiTestCase { mRow.getPrivateLayout().removeAllViews(); mRow.getEntry().getSbn().getNotification().contentView = new RemoteViews(mContext.getPackageName(), R.layout.invalid_notification_height); - inflateAndWait(true, mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow); + inflateAndWait(true, mNotificationInflater, FLAG_CONTENT_VIEW_ALL, REDACTION_TYPE_NONE, + mRow); assertEquals(0, mRow.getPrivateLayout().getChildCount()); verify(mRow, times(0)).onNotificationUpdated(); } @@ -395,12 +407,11 @@ public class NotificationContentInflaterTest extends SysuiTestCase { public void testExtractsPromotedContent_notWhenBothFlagsDisabled() throws Exception { final PromotedNotificationContentModel content = new PromotedNotificationContentModel.Builder("key").build(); - when(mPromotedNotificationContentExtractor.extractContent(any(), any())) - .thenReturn(content); + mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content); inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow); - verify(mPromotedNotificationContentExtractor, never()).extractContent(any(), any()); + mPromotedNotificationContentExtractor.verifyZeroExtractCalls(); } @Test @@ -410,12 +421,11 @@ public class NotificationContentInflaterTest extends SysuiTestCase { throws Exception { final PromotedNotificationContentModel content = new PromotedNotificationContentModel.Builder("key").build(); - when(mPromotedNotificationContentExtractor.extractContent(any(), any())) - .thenReturn(content); + mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content); inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow); - verify(mPromotedNotificationContentExtractor, times(1)).extractContent(any(), any()); + mPromotedNotificationContentExtractor.verifyOneExtractCall(); assertEquals(content, mRow.getEntry().getPromotedNotificationContentModel()); } @@ -425,12 +435,11 @@ public class NotificationContentInflaterTest extends SysuiTestCase { public void testExtractsPromotedContent_whenStatusBarNotifChipsFlagEnabled() throws Exception { final PromotedNotificationContentModel content = new PromotedNotificationContentModel.Builder("key").build(); - when(mPromotedNotificationContentExtractor.extractContent(any(), any())) - .thenReturn(content); + mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content); inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow); - verify(mPromotedNotificationContentExtractor, times(1)).extractContent(any(), any()); + mPromotedNotificationContentExtractor.verifyOneExtractCall(); assertEquals(content, mRow.getEntry().getPromotedNotificationContentModel()); } @@ -439,36 +448,107 @@ public class NotificationContentInflaterTest extends SysuiTestCase { public void testExtractsPromotedContent_whenBothFlagsEnabled() throws Exception { final PromotedNotificationContentModel content = new PromotedNotificationContentModel.Builder("key").build(); - when(mPromotedNotificationContentExtractor.extractContent(any(), any())) - .thenReturn(content); + mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content); inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow); - verify(mPromotedNotificationContentExtractor, times(1)).extractContent(any(), any()); + mPromotedNotificationContentExtractor.verifyOneExtractCall(); assertEquals(content, mRow.getEntry().getPromotedNotificationContentModel()); } @Test @EnableFlags({PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME}) public void testExtractsPromotedContent_null() throws Exception { - when(mPromotedNotificationContentExtractor.extractContent(any(), any())).thenReturn(null); + mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), null); inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow); - verify(mPromotedNotificationContentExtractor, times(1)).extractContent(any(), any()); + mPromotedNotificationContentExtractor.verifyOneExtractCall(); assertNull(mRow.getEntry().getPromotedNotificationContentModel()); } + @Test + @EnableFlags(LockscreenOtpRedaction.FLAG_NAME) + public void testSensitiveContentPublicView_messageStyle() throws Exception { + String displayName = "Display Name"; + String messageText = "Message Text"; + String contentText = "Content Text"; + Icon personIcon = Icon.createWithResource(mContext, + com.android.systemui.res.R.drawable.ic_person); + Person testPerson = new Person.Builder() + .setName(displayName) + .setIcon(personIcon) + .build(); + Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle(testPerson); + messagingStyle.addMessage(new Notification.MessagingStyle.Message(messageText, + System.currentTimeMillis(), testPerson)); + messagingStyle.setConversationType(Notification.MessagingStyle.CONVERSATION_TYPE_NORMAL); + messagingStyle.setShortcutIcon(personIcon); + Notification messageNotif = new Notification.Builder(mContext).setSmallIcon( + com.android.systemui.res.R.drawable.ic_person).setStyle(messagingStyle).build(); + ExpandableNotificationRow row = mHelper.createRow(messageNotif); + inflateAndWait(false, mNotificationInflater, FLAG_CONTENT_VIEW_PUBLIC, + REDACTION_TYPE_SENSITIVE_CONTENT, row); + NotificationContentView publicView = row.getPublicLayout(); + assertNotNull(publicView); + // The display name should be included, but not the content or message text + assertFalse(hasText(publicView, messageText)); + assertFalse(hasText(publicView, contentText)); + assertTrue(hasText(publicView, displayName)); + } + + @Test + @EnableFlags(LockscreenOtpRedaction.FLAG_NAME) + public void testSensitiveContentPublicView_nonMessageStyle() throws Exception { + String contentTitle = "Content Title"; + String contentText = "Content Text"; + Notification notif = new Notification.Builder(mContext).setSmallIcon( + com.android.systemui.res.R.drawable.ic_person) + .setContentTitle(contentTitle) + .setContentText(contentText) + .build(); + ExpandableNotificationRow row = mHelper.createRow(notif); + inflateAndWait(false, mNotificationInflater, FLAG_CONTENT_VIEW_PUBLIC, + REDACTION_TYPE_SENSITIVE_CONTENT, row); + NotificationContentView publicView = row.getPublicLayout(); + assertNotNull(publicView); + assertFalse(hasText(publicView, contentText)); + assertTrue(hasText(publicView, contentTitle)); + + // The standard public view should not use the content title or text + inflateAndWait(false, mNotificationInflater, FLAG_CONTENT_VIEW_PUBLIC, + REDACTION_TYPE_PUBLIC, row); + publicView = row.getPublicLayout(); + assertFalse(hasText(publicView, contentText)); + assertFalse(hasText(publicView, contentTitle)); + } + + private static boolean hasText(ViewGroup parent, CharSequence text) { + for (int i = 0; i < parent.getChildCount(); i++) { + View child = parent.getChildAt(i); + if (child instanceof ViewGroup) { + if (hasText((ViewGroup) child, text)) { + return true; + } + } else if (child instanceof TextView) { + return ((TextView) child).getText().toString().contains(text); + } + } + return false; + } + private static void inflateAndWait(NotificationContentInflater inflater, @InflationFlag int contentToInflate, ExpandableNotificationRow row) throws Exception { - inflateAndWait(false /* expectingException */, inflater, contentToInflate, row); + inflateAndWait(false /* expectingException */, inflater, contentToInflate, + REDACTION_TYPE_NONE, row); } private static void inflateAndWait(boolean expectingException, NotificationContentInflater inflater, @InflationFlag int contentToInflate, + @RedactionType int redactionType, ExpandableNotificationRow row) throws Exception { CountDownLatch countDownLatch = new CountDownLatch(1); final ExceptionHolder exceptionHolder = new ExceptionHolder(); @@ -496,7 +576,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { row.getEntry(), row, contentToInflate, - new BindParams(), + new BindParams(false, false, false, redactionType), false /* forceInflate */, callback /* callback */); assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS)); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt index 18517998096a..f25ba2c93c65 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.row import android.app.Notification import android.app.Person import android.content.Context +import android.graphics.drawable.Icon import android.os.AsyncTask import android.os.Build import android.os.CancellationSignal @@ -34,10 +35,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.res.R +import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE +import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_PUBLIC +import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT +import com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.notification.ConversationNotificationProcessor import com.android.systemui.statusbar.notification.collection.NotificationEntry -import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor +import com.android.systemui.statusbar.notification.promoted.FakePromotedNotificationContentExtractor import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams @@ -45,6 +50,7 @@ import com.android.systemui.statusbar.notification.row.NotificationRowContentBin import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag @@ -67,7 +73,6 @@ import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.eq import org.mockito.kotlin.mock -import org.mockito.kotlin.never import org.mockito.kotlin.spy import org.mockito.kotlin.times import org.mockito.kotlin.verify @@ -110,7 +115,8 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { return inflatedSmartReplyState } } - private val promotedNotificationContentExtractor: PromotedNotificationContentExtractor = mock() + private val promotedNotificationContentExtractor = FakePromotedNotificationContentExtractor() + private val conversationNotificationProcessor: ConversationNotificationProcessor = mock() @Before fun setUp() { @@ -127,7 +133,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { NotificationRowContentBinderImpl( cache, mock(), - mock<ConversationNotificationProcessor>(), + conversationNotificationProcessor, mock(), smartReplyStateInflater, layoutInflaterFactoryProvider, @@ -139,14 +145,14 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { @Test fun testIncreasedHeadsUpBeingUsed() { - val params = BindParams() - params.usesIncreasedHeadsUpHeight = true + val params = + BindParams(false, false, /* usesIncreasedHeadsUpHeight */ true, REDACTION_TYPE_NONE) val builder = spy(builder) notificationInflater.inflateNotificationViews( row.entry, row, params, - true /* inflateSynchronously */, + true, /* inflateSynchronously */ FLAG_CONTENT_VIEW_ALL, builder, mContext, @@ -158,14 +164,13 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { @Test fun testIncreasedHeightBeingUsed() { - val params = BindParams() - params.usesIncreasedHeight = true + val params = BindParams(false, /* usesIncreasedHeight */ true, false, REDACTION_TYPE_NONE) val builder = spy(builder) notificationInflater.inflateNotificationViews( row.entry, row, params, - true /* inflateSynchronously */, + true, /* inflateSynchronously */ FLAG_CONTENT_VIEW_ALL, builder, mContext, @@ -194,15 +199,18 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { row.entry.sbn.notification.contentView = RemoteViews(mContext.packageName, R.layout.status_bar) inflateAndWait( - true /* expectingException */, + true, /* expectingException */ notificationInflater, FLAG_CONTENT_VIEW_ALL, + REDACTION_TYPE_NONE, row, ) Assert.assertTrue(row.privateLayout.childCount == 0) verify(row, times(0)).onNotificationUpdated() } + @Test fun testInflationOfSensitiveContentPublicView() {} + @Test fun testAsyncTaskRemoved() { row.entry.abortTask() @@ -218,8 +226,8 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { row.entry, row, FLAG_CONTENT_VIEW_ALL, - BindParams(), - false /* forceInflate */, + BindParams(false, false, false, REDACTION_TYPE_NONE), + false, /* forceInflate */ null, /* callback */ ) Assert.assertNull(row.entry.runningTask) @@ -234,7 +242,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { packageContext = mContext, remoteViews = NewRemoteViews(), contentModel = NotificationContentModel(headsUpStatusBarModel), - extractedPromotedNotificationContentModel = null, + promotedContent = null, ) val countDownLatch = CountDownLatch(1) NotificationRowContentBinderImpl.applyRemoteView( @@ -432,7 +440,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { mContext.packageName, com.android.systemui.tests.R.layout.invalid_notification_height, ) - inflateAndWait(true, notificationInflater, FLAG_CONTENT_VIEW_ALL, row) + inflateAndWait(true, notificationInflater, FLAG_CONTENT_VIEW_ALL, REDACTION_TYPE_NONE, row) Assert.assertEquals(0, row.privateLayout.childCount.toLong()) verify(row, times(0)).onNotificationUpdated() } @@ -441,7 +449,13 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { @Test fun testInflatePublicSingleLineView() { row.publicLayout.removeAllViews() - inflateAndWait(false, notificationInflater, FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE, row) + inflateAndWait( + false, + notificationInflater, + FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE, + REDACTION_TYPE_NONE, + row, + ) Assert.assertNotNull(row.publicLayout.mSingleLineView) Assert.assertTrue(row.publicLayout.mSingleLineView is HybridNotificationView) } @@ -449,12 +463,15 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { @Test fun testInflatePublicSingleLineConversationView() { val testPerson = Person.Builder().setName("Person").build() + val style = Notification.MessagingStyle(testPerson) val messagingBuilder = Notification.Builder(mContext, "no-id") .setSmallIcon(R.drawable.ic_person) .setContentTitle("Title") .setContentText("Text") - .setStyle(Notification.MessagingStyle(testPerson)) + .setStyle(style) + whenever(conversationNotificationProcessor.processNotification(any(), any(), any())) + .thenReturn(style) val messagingRow = spy(testHelper.createRow(messagingBuilder.build())) messagingRow.publicLayout.removeAllViews() @@ -462,6 +479,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { false, notificationInflater, FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE, + REDACTION_TYPE_NONE, messagingRow, ) Assert.assertNotNull(messagingRow.publicLayout.mSingleLineView) @@ -475,12 +493,11 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { @DisableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) fun testExtractsPromotedContent_notWhenBothFlagsDisabled() { val content = PromotedNotificationContentModel.Builder("key").build() - whenever(promotedNotificationContentExtractor.extractContent(any(), any())) - .thenReturn(content) + promotedNotificationContentExtractor.resetForEntry(row.entry, content) inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row) - verify(promotedNotificationContentExtractor, never()).extractContent(any(), any()) + promotedNotificationContentExtractor.verifyZeroExtractCalls() } @Test @@ -488,12 +505,11 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { @DisableFlags(StatusBarNotifChips.FLAG_NAME) fun testExtractsPromotedContent_whenPromotedNotificationUiFlagEnabled() { val content = PromotedNotificationContentModel.Builder("key").build() - whenever(promotedNotificationContentExtractor.extractContent(any(), any())) - .thenReturn(content) + promotedNotificationContentExtractor.resetForEntry(row.entry, content) inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row) - verify(promotedNotificationContentExtractor, times(1)).extractContent(any(), any()) + promotedNotificationContentExtractor.verifyOneExtractCall() Assert.assertEquals(content, row.entry.promotedNotificationContentModel) } @@ -502,12 +518,11 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { @DisableFlags(PromotedNotificationUi.FLAG_NAME) fun testExtractsPromotedContent_whenStatusBarNotifChipsFlagEnabled() { val content = PromotedNotificationContentModel.Builder("key").build() - whenever(promotedNotificationContentExtractor.extractContent(any(), any())) - .thenReturn(content) + promotedNotificationContentExtractor.resetForEntry(row.entry, content) inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row) - verify(promotedNotificationContentExtractor, times(1)).extractContent(any(), any()) + promotedNotificationContentExtractor.verifyOneExtractCall() Assert.assertEquals(content, row.entry.promotedNotificationContentModel) } @@ -515,15 +530,99 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) fun testExtractsPromotedContent_whenBothFlagsEnabled() { val content = PromotedNotificationContentModel.Builder("key").build() - whenever(promotedNotificationContentExtractor.extractContent(any(), any())) - .thenReturn(content) + promotedNotificationContentExtractor.resetForEntry(row.entry, content) inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row) - verify(promotedNotificationContentExtractor, times(1)).extractContent(any(), any()) + promotedNotificationContentExtractor.verifyOneExtractCall() Assert.assertEquals(content, row.entry.promotedNotificationContentModel) } + @Test + @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) + fun testExtractsPromotedContent_null() { + promotedNotificationContentExtractor.resetForEntry(row.entry, null) + + inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row) + + promotedNotificationContentExtractor.verifyOneExtractCall() + Assert.assertNull(row.entry.promotedNotificationContentModel) + } + + @Test + @Throws(java.lang.Exception::class) + @EnableFlags(LockscreenOtpRedaction.FLAG_NAME) + fun testSensitiveContentPublicView_messageStyle() { + val displayName = "Display Name" + val messageText = "Message Text" + val contentText = "Content Text" + val personIcon = Icon.createWithResource(mContext, R.drawable.ic_person) + val testPerson = Person.Builder().setName(displayName).setIcon(personIcon).build() + val messagingStyle = Notification.MessagingStyle(testPerson) + messagingStyle.addMessage( + Notification.MessagingStyle.Message(messageText, System.currentTimeMillis(), testPerson) + ) + messagingStyle.setConversationType(Notification.MessagingStyle.CONVERSATION_TYPE_NORMAL) + messagingStyle.setShortcutIcon(personIcon) + val messageNotif = + Notification.Builder(mContext) + .setSmallIcon(R.drawable.ic_person) + .setStyle(messagingStyle) + .build() + val newRow: ExpandableNotificationRow = testHelper.createRow(messageNotif) + inflateAndWait( + false, + notificationInflater, + FLAG_CONTENT_VIEW_PUBLIC, + REDACTION_TYPE_SENSITIVE_CONTENT, + newRow, + ) + // The display name should be included, but not the content or message text + val publicView = newRow.publicLayout + Assert.assertNotNull(publicView) + Assert.assertFalse(hasText(publicView, messageText)) + Assert.assertFalse(hasText(publicView, contentText)) + Assert.assertTrue(hasText(publicView, displayName)) + } + + @Test + @Throws(java.lang.Exception::class) + @EnableFlags(LockscreenOtpRedaction.FLAG_NAME) + fun testSensitiveContentPublicView_nonMessageStyle() { + val contentTitle = "Content Title" + val contentText = "Content Text" + val notif = + Notification.Builder(mContext) + .setSmallIcon(R.drawable.ic_person) + .setContentTitle(contentTitle) + .setContentText(contentText) + .build() + val newRow: ExpandableNotificationRow = testHelper.createRow(notif) + inflateAndWait( + false, + notificationInflater, + FLAG_CONTENT_VIEW_PUBLIC, + REDACTION_TYPE_SENSITIVE_CONTENT, + newRow, + ) + var publicView = newRow.publicLayout + Assert.assertNotNull(publicView) + Assert.assertFalse(hasText(publicView, contentText)) + Assert.assertTrue(hasText(publicView, contentTitle)) + + // The standard public view should not use the content title or text + inflateAndWait( + false, + notificationInflater, + FLAG_CONTENT_VIEW_PUBLIC, + REDACTION_TYPE_PUBLIC, + newRow, + ) + publicView = newRow.publicLayout + Assert.assertFalse(hasText(publicView, contentText)) + Assert.assertFalse(hasText(publicView, contentTitle)) + } + private class ExceptionHolder { var exception: Exception? = null } @@ -562,13 +661,20 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { @InflationFlag contentToInflate: Int, row: ExpandableNotificationRow, ) { - inflateAndWait(false /* expectingException */, inflater, contentToInflate, row) + inflateAndWait( + false /* expectingException */, + inflater, + contentToInflate, + REDACTION_TYPE_NONE, + row, + ) } private fun inflateAndWait( expectingException: Boolean, inflater: NotificationRowContentBinderImpl, @InflationFlag contentToInflate: Int, + @RedactionType redactionType: Int, row: ExpandableNotificationRow, ) { val countDownLatch = CountDownLatch(1) @@ -597,12 +703,26 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { row.entry, row, contentToInflate, - BindParams(), - false /* forceInflate */, + BindParams(false, false, false, redactionType), + false, /* forceInflate */ callback, /* callback */ ) Assert.assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS)) exceptionHolder.exception?.let { throw it } } + + fun hasText(parent: ViewGroup, text: CharSequence): Boolean { + for (i in 0 until parent.childCount) { + val child = parent.getChildAt(i) + if (child is ViewGroup) { + if (hasText(child, text)) { + return true + } + } else if (child is TextView) { + return child.text.toString().contains(text) + } + } + return false + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index b323ef85b370..b8d18757afbb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -81,7 +81,7 @@ import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.notification.icon.IconBuilder; import com.android.systemui.statusbar.notification.icon.IconManager; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; -import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor; +import com.android.systemui.statusbar.notification.promoted.FakePromotedNotificationContentExtractor; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; @@ -201,7 +201,7 @@ public class NotificationTestHelper { new MockSmartReplyInflater(), mock(NotifLayoutInflaterFactory.Provider.class), mock(HeadsUpStyleProvider.class), - mock(PromotedNotificationContentExtractor.class), + new FakePromotedNotificationContentExtractor(), mock(NotificationRowContentBinderLogger.class)) : new NotificationContentInflater( mock(NotifRemoteViewCache.class), @@ -212,7 +212,7 @@ public class NotificationTestHelper { new MockSmartReplyInflater(), mock(NotifLayoutInflaterFactory.Provider.class), mock(HeadsUpStyleProvider.class), - mock(PromotedNotificationContentExtractor.class), + new FakePromotedNotificationContentExtractor(), mock(NotificationRowContentBinderLogger.class)); contentBinder.setInflateSynchronously(true); mBindStage = new RowContentBindStage(contentBinder, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index de40abb4d9d8..c6cffa9da13b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -952,11 +952,10 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Test @EnableSceneContainer public void onTouchEvent_stopExpandingNotification_sceneContainerEnabled() { - boolean touchHandled = stopExpandingNotification(); + stopExpandingNotification(); - verify(mNotificationStackScrollLayout).startOverscrollAfterExpanding(); + verify(mExpandHelper).finishExpanding(); verify(mNotificationStackScrollLayout, never()).dispatchDownEventToScroller(any()); - assertTrue(touchHandled); } @Test @@ -964,11 +963,11 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { public void onTouchEvent_stopExpandingNotification_sceneContainerDisabled() { stopExpandingNotification(); - verify(mNotificationStackScrollLayout, never()).startOverscrollAfterExpanding(); + verify(mExpandHelper, never()).finishExpanding(); verify(mNotificationStackScrollLayout).dispatchDownEventToScroller(any()); } - private boolean stopExpandingNotification() { + private void stopExpandingNotification() { when(mNotificationStackScrollLayout.getExpandHelper()).thenReturn(mExpandHelper); when(mNotificationStackScrollLayout.getIsExpanded()).thenReturn(true); when(mNotificationStackScrollLayout.getExpandedInThisMotion()).thenReturn(true); @@ -983,13 +982,13 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { NotificationStackScrollLayoutController.TouchHandler touchHandler = mController.getTouchHandler(); - return touchHandler.onTouchEvent(MotionEvent.obtain( - /* downTime= */ 0, - /* eventTime= */ 0, - MotionEvent.ACTION_DOWN, - 0, - 0, - /* metaState= */ 0 + touchHandler.onTouchEvent(MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 0, + MotionEvent.ACTION_DOWN, + 0, + 0, + /* metaState= */ 0 )); } 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 f48fd3c998b1..6bdd86efa8c0 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 @@ -241,7 +241,7 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S shadeTestUtil.setSplitShade(true) val horizontalPosition = checkNotNull(dimens).horizontalPosition - assertIs<HorizontalPosition.FloatAtEnd>(horizontalPosition) + assertIs<HorizontalPosition.FloatAtStart>(horizontalPosition) assertThat(horizontalPosition.width).isEqualTo(200) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java new file mode 100644 index 000000000000..41782a123f14 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2018 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.phone; + +import static android.view.Display.DEFAULT_DISPLAY; + +import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE; +import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK; +import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Notification; +import android.app.PendingIntent; +import android.app.StatusBarManager; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.testing.TestableLooper; +import android.testing.TestableLooper.RunWithLooper; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.systemui.InitController; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.power.domain.interactor.PowerInteractor; +import com.android.systemui.settings.FakeDisplayTracker; +import com.android.systemui.shade.NotificationShadeWindowView; +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; +import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.notification.DynamicPrivacyController; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; +import com.android.systemui.statusbar.notification.domain.interactor.NotificationAlertsInteractor; +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionCondition; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionFilter; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType; +import com.android.systemui.statusbar.notification.row.NotificationGutsManager; +import com.android.systemui.statusbar.notification.stack.NotificationListContainer; +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; +import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; +import com.android.systemui.statusbar.policy.KeyguardStateController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; + +import java.util.List; +import java.util.Set; + +@SmallTest +@RunWith(AndroidJUnit4.class) +@RunWithLooper() +public class StatusBarNotificationPresenterTest extends SysuiTestCase { + private StatusBarNotificationPresenter mStatusBarNotificationPresenter; + private final VisualInterruptionDecisionProvider mVisualInterruptionDecisionProvider = + mock(VisualInterruptionDecisionProvider.class); + private NotificationInterruptSuppressor mInterruptSuppressor; + private VisualInterruptionCondition mAlertsDisabledCondition; + private VisualInterruptionCondition mVrModeCondition; + private VisualInterruptionFilter mNeedsRedactionFilter; + private VisualInterruptionCondition mPanelsDisabledCondition; + private CommandQueue mCommandQueue; + private final ShadeController mShadeController = mock(ShadeController.class); + private final NotificationAlertsInteractor mNotificationAlertsInteractor = + mock(NotificationAlertsInteractor.class); + private final KeyguardStateController mKeyguardStateController = + mock(KeyguardStateController.class); + + @Before + public void setup() { + mCommandQueue = new CommandQueue(mContext, new FakeDisplayTracker(mContext)); + mDependency.injectTestDependency(StatusBarStateController.class, + mock(SysuiStatusBarStateController.class)); + mDependency.injectTestDependency(ShadeController.class, mShadeController); + mDependency.injectMockDependency(NotificationRemoteInputManager.Callback.class); + mDependency.injectMockDependency(NotificationShadeWindowController.class); + + when(mNotificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(true); + + createPresenter(); + if (VisualInterruptionRefactor.isEnabled()) { + verifyAndCaptureSuppressors(); + } else { + verifyAndCaptureLegacySuppressor(); + } + } + + @Test + @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testInit_refactorDisabled() { + assertFalse(VisualInterruptionRefactor.isEnabled()); + assertNull(mAlertsDisabledCondition); + assertNull(mVrModeCondition); + assertNull(mNeedsRedactionFilter); + assertNull(mPanelsDisabledCondition); + assertNotNull(mInterruptSuppressor); + } + + @Test + @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testInit_refactorEnabled() { + assertTrue(VisualInterruptionRefactor.isEnabled()); + assertNotNull(mAlertsDisabledCondition); + assertNotNull(mVrModeCondition); + assertNotNull(mNeedsRedactionFilter); + assertNotNull(mPanelsDisabledCondition); + assertNull(mInterruptSuppressor); + } + + @Test + @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testNoSuppressHeadsUp_default_refactorDisabled() { + assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(createNotificationEntry())); + } + + @Test + @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testNoSuppressHeadsUp_default_refactorEnabled() { + assertFalse(mAlertsDisabledCondition.shouldSuppress()); + assertFalse(mVrModeCondition.shouldSuppress()); + assertFalse(mNeedsRedactionFilter.shouldSuppress(createNotificationEntry())); + assertFalse(mAlertsDisabledCondition.shouldSuppress()); + } + + @Test + @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testSuppressHeadsUp_disabledStatusBar_refactorDisabled() { + mCommandQueue.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_EXPAND, 0, + false /* animate */); + TestableLooper.get(this).processAllMessages(); + + assertTrue("The panel should suppress heads up while disabled", + mInterruptSuppressor.suppressAwakeHeadsUp(createNotificationEntry())); + } + + @Test + @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testSuppressHeadsUp_disabledStatusBar_refactorEnabled() { + mCommandQueue.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_EXPAND, 0, + false /* animate */); + TestableLooper.get(this).processAllMessages(); + + assertTrue("The panel should suppress heads up while disabled", + mPanelsDisabledCondition.shouldSuppress()); + } + + @Test + @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testSuppressHeadsUp_disabledNotificationShade_refactorDisabled() { + mCommandQueue.disable(DEFAULT_DISPLAY, 0, StatusBarManager.DISABLE2_NOTIFICATION_SHADE, + false /* animate */); + TestableLooper.get(this).processAllMessages(); + + assertTrue("The panel should suppress interruptions while notification shade disabled", + mInterruptSuppressor.suppressAwakeHeadsUp(createNotificationEntry())); + } + + @Test + @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testSuppressHeadsUp_disabledNotificationShade_refactorEnabled() { + mCommandQueue.disable(DEFAULT_DISPLAY, 0, StatusBarManager.DISABLE2_NOTIFICATION_SHADE, + false /* animate */); + TestableLooper.get(this).processAllMessages(); + + assertTrue("The panel should suppress interruptions while notification shade disabled", + mPanelsDisabledCondition.shouldSuppress()); + } + + @Test + @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testPanelsDisabledConditionSuppressesPeek() { + final Set<VisualInterruptionType> types = mPanelsDisabledCondition.getTypes(); + assertTrue(types.contains(PEEK)); + assertFalse(types.contains(PULSE)); + assertFalse(types.contains(BUBBLE)); + } + + @Test + @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testNoSuppressHeadsUp_FSI_nonOccludedKeyguard_refactorDisabled() { + when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mKeyguardStateController.isOccluded()).thenReturn(false); + + assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(createFsiNotificationEntry())); + } + + @Test + @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testNoSuppressHeadsUp_FSI_nonOccludedKeyguard_refactorEnabled() { + when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mKeyguardStateController.isOccluded()).thenReturn(false); + + assertFalse(mNeedsRedactionFilter.shouldSuppress(createFsiNotificationEntry())); + + final Set<VisualInterruptionType> types = mNeedsRedactionFilter.getTypes(); + assertTrue(types.contains(PEEK)); + assertFalse(types.contains(PULSE)); + assertFalse(types.contains(BUBBLE)); + } + + @Test + @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testSuppressInterruptions_vrMode_refactorDisabled() { + mStatusBarNotificationPresenter.mVrMode = true; + + assertTrue("Vr mode should suppress interruptions", + mInterruptSuppressor.suppressAwakeInterruptions(createNotificationEntry())); + } + + @Test + @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testSuppressInterruptions_vrMode_refactorEnabled() { + mStatusBarNotificationPresenter.mVrMode = true; + + assertTrue("Vr mode should suppress interruptions", mVrModeCondition.shouldSuppress()); + + final Set<VisualInterruptionType> types = mVrModeCondition.getTypes(); + assertTrue(types.contains(PEEK)); + assertFalse(types.contains(PULSE)); + assertTrue(types.contains(BUBBLE)); + } + + @Test + @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testSuppressInterruptions_statusBarAlertsDisabled_refactorDisabled() { + when(mNotificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false); + + assertTrue("When alerts aren't enabled, interruptions are suppressed", + mInterruptSuppressor.suppressInterruptions(createNotificationEntry())); + } + + @Test + @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testSuppressInterruptions_statusBarAlertsDisabled_refactorEnabled() { + when(mNotificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false); + + assertTrue("When alerts aren't enabled, interruptions are suppressed", + mAlertsDisabledCondition.shouldSuppress()); + + final Set<VisualInterruptionType> types = mAlertsDisabledCondition.getTypes(); + assertTrue(types.contains(PEEK)); + assertTrue(types.contains(PULSE)); + assertTrue(types.contains(BUBBLE)); + } + + private void createPresenter() { + final ShadeViewController shadeViewController = mock(ShadeViewController.class); + + final NotificationShadeWindowView notificationShadeWindowView = + mock(NotificationShadeWindowView.class); + when(notificationShadeWindowView.getResources()).thenReturn(mContext.getResources()); + + NotificationStackScrollLayoutController stackScrollLayoutController = + mock(NotificationStackScrollLayoutController.class); + when(stackScrollLayoutController.getView()).thenReturn( + mock(NotificationStackScrollLayout.class)); + + final InitController initController = new InitController(); + + mStatusBarNotificationPresenter = new StatusBarNotificationPresenter( + mContext, + shadeViewController, + mock(PanelExpansionInteractor.class), + mock(QuickSettingsController.class), + mock(HeadsUpManager.class), + notificationShadeWindowView, + mock(ActivityStarter.class), + stackScrollLayoutController, + mock(DozeScrimController.class), + mock(NotificationShadeWindowController.class), + mock(DynamicPrivacyController.class), + mKeyguardStateController, + mNotificationAlertsInteractor, + mock(LockscreenShadeTransitionController.class), + mock(PowerInteractor.class), + mCommandQueue, + mock(NotificationLockscreenUserManager.class), + mock(SysuiStatusBarStateController.class), + mock(NotifShadeEventSource.class), + mock(NotificationMediaManager.class), + mock(NotificationGutsManager.class), + initController, + mVisualInterruptionDecisionProvider, + mock(NotificationRemoteInputManager.class), + mock(NotificationRemoteInputManager.Callback.class), + mock(NotificationListContainer.class)); + + initController.executePostInitTasks(); + } + + private void verifyAndCaptureSuppressors() { + mInterruptSuppressor = null; + + final ArgumentCaptor<VisualInterruptionCondition> conditionCaptor = + ArgumentCaptor.forClass(VisualInterruptionCondition.class); + verify(mVisualInterruptionDecisionProvider, times(3)).addCondition( + conditionCaptor.capture()); + final List<VisualInterruptionCondition> conditions = conditionCaptor.getAllValues(); + mAlertsDisabledCondition = conditions.get(0); + mVrModeCondition = conditions.get(1); + mPanelsDisabledCondition = conditions.get(2); + + final ArgumentCaptor<VisualInterruptionFilter> needsRedactionFilterCaptor = + ArgumentCaptor.forClass(VisualInterruptionFilter.class); + verify(mVisualInterruptionDecisionProvider).addFilter(needsRedactionFilterCaptor.capture()); + mNeedsRedactionFilter = needsRedactionFilterCaptor.getValue(); + } + + private void verifyAndCaptureLegacySuppressor() { + mAlertsDisabledCondition = null; + mVrModeCondition = null; + mNeedsRedactionFilter = null; + mPanelsDisabledCondition = null; + + final ArgumentCaptor<NotificationInterruptSuppressor> suppressorCaptor = + ArgumentCaptor.forClass(NotificationInterruptSuppressor.class); + verify(mVisualInterruptionDecisionProvider).addLegacySuppressor(suppressorCaptor.capture()); + mInterruptSuppressor = suppressorCaptor.getValue(); + } + + private NotificationEntry createNotificationEntry() { + return new NotificationEntryBuilder() + .setPkg("a") + .setOpPkg("a") + .setTag("a") + .setNotification(new Notification.Builder(getContext(), "a").build()) + .build(); + } + + private NotificationEntry createFsiNotificationEntry() { + final Notification notification = new Notification.Builder(getContext(), "a") + .setFullScreenIntent(mock(PendingIntent.class), true) + .build(); + + return new NotificationEntryBuilder() + .setPkg("a") + .setOpPkg("a") + .setTag("a") + .setNotification(notification) + .build(); + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.kt deleted file mode 100644 index c347347eff83..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.kt +++ /dev/null @@ -1,454 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.statusbar.phone - -import android.app.Notification -import android.app.Notification.Builder -import android.app.StatusBarManager -import android.platform.test.annotations.DisableFlags -import android.platform.test.annotations.EnableFlags -import android.testing.TestableLooper -import android.testing.TestableLooper.RunWithLooper -import android.view.Display.DEFAULT_DISPLAY -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.systemui.InitController -import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel -import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor -import com.android.systemui.flags.EnableSceneContainer -import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.runTest -import com.android.systemui.plugins.activityStarter -import com.android.systemui.power.domain.interactor.powerInteractor -import com.android.systemui.settings.FakeDisplayTracker -import com.android.systemui.shade.NotificationShadeWindowView -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.StatusBarState -import com.android.systemui.statusbar.lockscreenShadeTransitionController -import com.android.systemui.statusbar.notification.collection.NotificationEntry -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder -import com.android.systemui.statusbar.notification.domain.interactor.notificationAlertsInteractor -import com.android.systemui.statusbar.notification.dynamicPrivacyController -import com.android.systemui.statusbar.notification.headsup.headsUpManager -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor -import com.android.systemui.statusbar.notification.interruption.VisualInterruptionCondition -import com.android.systemui.statusbar.notification.interruption.VisualInterruptionFilter -import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor -import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow -import com.android.systemui.statusbar.notification.stack.notificationStackScrollLayoutController -import com.android.systemui.statusbar.notification.visualInterruptionDecisionProvider -import com.android.systemui.statusbar.notificationLockscreenUserManager -import com.android.systemui.statusbar.notificationRemoteInputManager -import com.android.systemui.statusbar.notificationShadeWindowController -import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.statusbar.policy.keyguardStateController -import com.android.systemui.statusbar.sysuiStatusBarStateController -import com.android.systemui.testKosmos -import com.google.common.truth.Truth.assertThat -import com.google.common.truth.Truth.assertWithMessage -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.kotlin.any -import org.mockito.kotlin.argumentCaptor -import org.mockito.kotlin.eq -import org.mockito.kotlin.mock -import org.mockito.kotlin.times -import org.mockito.kotlin.verify -import org.mockito.kotlin.whenever - -@SmallTest -@RunWith(AndroidJUnit4::class) -@RunWithLooper -class StatusBarNotificationPresenterTest : SysuiTestCase() { - private lateinit var kosmos: Kosmos - - private var interruptSuppressor: NotificationInterruptSuppressor? = null - private var alertsDisabledCondition: VisualInterruptionCondition? = null - private var vrModeCondition: VisualInterruptionCondition? = null - private var needsRedactionFilter: VisualInterruptionFilter? = null - private var panelsDisabledCondition: VisualInterruptionCondition? = null - - private val commandQueue: CommandQueue = CommandQueue(mContext, FakeDisplayTracker(mContext)) - private val keyguardStateController: KeyguardStateController - get() = kosmos.keyguardStateController - - private val notificationAlertsInteractor - get() = kosmos.notificationAlertsInteractor - - private val visualInterruptionDecisionProvider - get() = kosmos.visualInterruptionDecisionProvider - - private lateinit var underTest: StatusBarNotificationPresenter - - @Before - fun setup() { - kosmos = - testKosmos().apply { - whenever(notificationAlertsInteractor.areNotificationAlertsEnabled()) - .thenReturn(true) - whenever(notificationStackScrollLayoutController.expandHelperCallback) - .thenReturn(mock()) - lockscreenShadeTransitionController.setStackScroller( - notificationStackScrollLayoutController - ) - lockscreenShadeTransitionController.centralSurfaces = mock() - } - - underTest = createPresenter() - if (VisualInterruptionRefactor.isEnabled) { - verifyAndCaptureSuppressors() - } else { - verifyAndCaptureLegacySuppressor() - } - } - - @Test - @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testInit_refactorDisabled() { - assertThat(VisualInterruptionRefactor.isEnabled).isFalse() - assertThat(alertsDisabledCondition).isNull() - assertThat(vrModeCondition).isNull() - assertThat(needsRedactionFilter).isNull() - assertThat(panelsDisabledCondition).isNull() - assertThat(interruptSuppressor).isNotNull() - } - - @Test - @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testInit_refactorEnabled() { - assertThat(VisualInterruptionRefactor.isEnabled).isTrue() - assertThat(alertsDisabledCondition).isNotNull() - assertThat(vrModeCondition).isNotNull() - assertThat(needsRedactionFilter).isNotNull() - assertThat(panelsDisabledCondition).isNotNull() - assertThat(interruptSuppressor).isNull() - } - - @Test - @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testNoSuppressHeadsUp_default_refactorDisabled() { - assertThat(interruptSuppressor!!.suppressAwakeHeadsUp(createNotificationEntry())).isFalse() - } - - @Test - @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testNoSuppressHeadsUp_default_refactorEnabled() { - assertThat(alertsDisabledCondition!!.shouldSuppress()).isFalse() - assertThat(vrModeCondition!!.shouldSuppress()).isFalse() - assertThat(needsRedactionFilter!!.shouldSuppress(createNotificationEntry())).isFalse() - assertThat(alertsDisabledCondition!!.shouldSuppress()).isFalse() - } - - @Test - @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testSuppressHeadsUp_disabledStatusBar_refactorDisabled() { - commandQueue.disable( - DEFAULT_DISPLAY, - StatusBarManager.DISABLE_EXPAND, - 0, - false, /* animate */ - ) - TestableLooper.get(this).processAllMessages() - - assertWithMessage("The panel should suppress heads up while disabled") - .that(interruptSuppressor!!.suppressAwakeHeadsUp(createNotificationEntry())) - .isTrue() - } - - @Test - @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testSuppressHeadsUp_disabledStatusBar_refactorEnabled() { - commandQueue.disable( - DEFAULT_DISPLAY, - StatusBarManager.DISABLE_EXPAND, - 0, - false, /* animate */ - ) - TestableLooper.get(this).processAllMessages() - - assertWithMessage("The panel should suppress heads up while disabled") - .that(panelsDisabledCondition!!.shouldSuppress()) - .isTrue() - } - - @Test - @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testSuppressHeadsUp_disabledNotificationShade_refactorDisabled() { - commandQueue.disable( - DEFAULT_DISPLAY, - 0, - StatusBarManager.DISABLE2_NOTIFICATION_SHADE, - false, /* animate */ - ) - TestableLooper.get(this).processAllMessages() - - assertWithMessage( - "The panel should suppress interruptions while notification shade disabled" - ) - .that(interruptSuppressor!!.suppressAwakeHeadsUp(createNotificationEntry())) - .isTrue() - } - - @Test - @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testSuppressHeadsUp_disabledNotificationShade_refactorEnabled() { - commandQueue.disable( - DEFAULT_DISPLAY, - 0, - StatusBarManager.DISABLE2_NOTIFICATION_SHADE, - false, /* animate */ - ) - TestableLooper.get(this).processAllMessages() - - assertWithMessage( - "The panel should suppress interruptions while notification shade disabled" - ) - .that(panelsDisabledCondition!!.shouldSuppress()) - .isTrue() - } - - @Test - @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testPanelsDisabledConditionSuppressesPeek() { - val types: Set<VisualInterruptionType> = panelsDisabledCondition!!.types - assertThat(types).contains(VisualInterruptionType.PEEK) - assertThat(types) - .containsNoneOf(VisualInterruptionType.BUBBLE, VisualInterruptionType.PULSE) - } - - @Test - @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testNoSuppressHeadsUp_FSI_nonOccludedKeyguard_refactorDisabled() { - whenever(keyguardStateController.isShowing()).thenReturn(true) - whenever(keyguardStateController.isOccluded()).thenReturn(false) - - assertThat(interruptSuppressor!!.suppressAwakeHeadsUp(createFsiNotificationEntry())) - .isFalse() - } - - @Test - @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testNoSuppressHeadsUp_FSI_nonOccludedKeyguard_refactorEnabled() { - whenever(keyguardStateController.isShowing()).thenReturn(true) - whenever(keyguardStateController.isOccluded()).thenReturn(false) - - assertThat(needsRedactionFilter!!.shouldSuppress(createFsiNotificationEntry())).isFalse() - - val types: Set<VisualInterruptionType> = needsRedactionFilter!!.types - assertThat(types).contains(VisualInterruptionType.PEEK) - assertThat(types) - .containsNoneOf(VisualInterruptionType.BUBBLE, VisualInterruptionType.PULSE) - } - - @Test - @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testSuppressInterruptions_vrMode_refactorDisabled() { - underTest.mVrMode = true - - assertWithMessage("Vr mode should suppress interruptions") - .that(interruptSuppressor!!.suppressAwakeInterruptions(createNotificationEntry())) - .isTrue() - } - - @Test - @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testSuppressInterruptions_vrMode_refactorEnabled() { - underTest.mVrMode = true - - assertWithMessage("Vr mode should suppress interruptions") - .that(vrModeCondition!!.shouldSuppress()) - .isTrue() - - val types: Set<VisualInterruptionType> = vrModeCondition!!.types - assertThat(types).contains(VisualInterruptionType.PEEK) - assertThat(types).doesNotContain(VisualInterruptionType.PULSE) - assertThat(types).contains(VisualInterruptionType.BUBBLE) - } - - @Test - @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testSuppressInterruptions_statusBarAlertsDisabled_refactorDisabled() { - whenever(notificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false) - - assertWithMessage("When alerts aren't enabled, interruptions are suppressed") - .that(interruptSuppressor!!.suppressInterruptions(createNotificationEntry())) - .isTrue() - } - - @Test - @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testSuppressInterruptions_statusBarAlertsDisabled_refactorEnabled() { - whenever(notificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false) - - assertWithMessage("When alerts aren't enabled, interruptions are suppressed") - .that(alertsDisabledCondition!!.shouldSuppress()) - .isTrue() - - val types: Set<VisualInterruptionType> = alertsDisabledCondition!!.types - assertThat(types).contains(VisualInterruptionType.PEEK) - assertThat(types).contains(VisualInterruptionType.PULSE) - assertThat(types).contains(VisualInterruptionType.BUBBLE) - } - - @Test - @EnableSceneContainer - fun testExpandSensitiveNotification_onLockScreen_opensShade() = - kosmos.runTest { - // Given we are on the keyguard - kosmos.sysuiStatusBarStateController.state = StatusBarState.KEYGUARD - // And the device is locked - kosmos.fakeAuthenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin - ) - - // When the user expands a sensitive Notification - val entry = - createRow().entry.apply { - setSensitive(/* sensitive= */ true, /* deviceSensitive= */ true) - } - underTest.onExpandClicked(entry, mock(), /* nowExpanded= */ true) - - // Then we open the locked shade - assertThat(kosmos.sysuiStatusBarStateController.state) - .isEqualTo(StatusBarState.SHADE_LOCKED) - } - - @Test - @EnableSceneContainer - fun testExpandSensitiveNotification_onLockedShade_showsBouncer() = - kosmos.runTest { - // Given we are on the locked shade - kosmos.sysuiStatusBarStateController.state = StatusBarState.SHADE_LOCKED - // And the device is locked - kosmos.fakeAuthenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin - ) - - // When the user expands a sensitive Notification - val entry = - createRow().entry.apply { - setSensitive(/* sensitive= */ true, /* deviceSensitive= */ true) - } - underTest.onExpandClicked(entry, mock(), /* nowExpanded= */ true) - - // Then we show the bouncer - verify(kosmos.activityStarter).dismissKeyguardThenExecute(any(), eq(null), eq(false)) - // AND we are still on the locked shade - assertThat(kosmos.sysuiStatusBarStateController.state) - .isEqualTo(StatusBarState.SHADE_LOCKED) - } - - private fun createPresenter(): StatusBarNotificationPresenter { - val shadeViewController: ShadeViewController = mock() - - val notificationShadeWindowView: NotificationShadeWindowView = mock() - whenever(notificationShadeWindowView.resources).thenReturn(mContext.resources) - whenever(kosmos.notificationStackScrollLayoutController.view).thenReturn(mock()) - - val initController: InitController = InitController() - - return StatusBarNotificationPresenter( - mContext, - shadeViewController, - kosmos.panelExpansionInteractor, - /* quickSettingsController = */ mock(), - kosmos.headsUpManager, - notificationShadeWindowView, - kosmos.activityStarter, - kosmos.notificationStackScrollLayoutController, - kosmos.dozeScrimController, - kosmos.notificationShadeWindowController, - kosmos.dynamicPrivacyController, - kosmos.keyguardStateController, - kosmos.notificationAlertsInteractor, - kosmos.lockscreenShadeTransitionController, - kosmos.powerInteractor, - commandQueue, - kosmos.notificationLockscreenUserManager, - kosmos.sysuiStatusBarStateController, - /* notifShadeEventSource = */ mock(), - /* notificationMediaManager = */ mock(), - /* notificationGutsManager = */ mock(), - initController, - kosmos.visualInterruptionDecisionProvider, - kosmos.notificationRemoteInputManager, - /* remoteInputManagerCallback = */ mock(), - /* notificationListContainer = */ mock(), - kosmos.deviceUnlockedInteractor, - ) - .also { initController.executePostInitTasks() } - } - - private fun verifyAndCaptureSuppressors() { - interruptSuppressor = null - - val conditionCaptor = argumentCaptor<VisualInterruptionCondition>() - verify(visualInterruptionDecisionProvider, times(3)).addCondition(conditionCaptor.capture()) - val conditions: List<VisualInterruptionCondition> = conditionCaptor.allValues - alertsDisabledCondition = conditions[0] - vrModeCondition = conditions[1] - panelsDisabledCondition = conditions[2] - - val needsRedactionFilterCaptor = argumentCaptor<VisualInterruptionFilter>() - verify(visualInterruptionDecisionProvider).addFilter(needsRedactionFilterCaptor.capture()) - needsRedactionFilter = needsRedactionFilterCaptor.lastValue - } - - private fun verifyAndCaptureLegacySuppressor() { - alertsDisabledCondition = null - vrModeCondition = null - needsRedactionFilter = null - panelsDisabledCondition = null - - val suppressorCaptor = argumentCaptor<NotificationInterruptSuppressor>() - verify(visualInterruptionDecisionProvider).addLegacySuppressor(suppressorCaptor.capture()) - interruptSuppressor = suppressorCaptor.lastValue - } - - private fun createRow(): ExpandableNotificationRow { - val row: ExpandableNotificationRow = mock() - val entry: NotificationEntry = createNotificationEntry() - whenever(row.entry).thenReturn(entry) - entry.row = row - return row - } - - private fun createNotificationEntry(): NotificationEntry = - NotificationEntryBuilder() - .setPkg("a") - .setOpPkg("a") - .setTag("a") - .setNotification(Builder(mContext, "a").build()) - .build() - - private fun createFsiNotificationEntry(): NotificationEntry { - val notification: Notification = - Builder(mContext, "a").setFullScreenIntent(mock(), true).build() - - return NotificationEntryBuilder() - .setPkg("a") - .setOpPkg("a") - .setTag("a") - .setNotification(notification) - .build() - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt index b560c591af1e..1ee8005fb7ab 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt @@ -20,7 +20,9 @@ import android.view.WindowManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any @@ -31,7 +33,6 @@ import kotlin.test.Test import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.runner.RunWith @@ -43,7 +44,7 @@ import org.mockito.Mockito.verify @RunWithLooper(setAsMainLooper = true) class SystemUIBottomSheetDialogTest : SysuiTestCase() { - private val kosmos = testKosmos() + private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val configurationController = mock<ConfigurationController>() private val config = mock<Configuration>() private val delegate = mock<DialogDelegate<Dialog>>() @@ -67,21 +68,17 @@ class SystemUIBottomSheetDialogTest : SysuiTestCase() { @Test fun onStart_registersConfigCallback() { - kosmos.testScope.runTest { + kosmos.runTest { dialog.show() - runCurrent() - verify(configurationController).addCallback(any()) } } @Test fun onStop_unregisterConfigCallback() { - kosmos.testScope.runTest { + kosmos.runTest { dialog.show() - runCurrent() dialog.dismiss() - runCurrent() verify(configurationController).removeCallback(any()) } @@ -89,14 +86,12 @@ class SystemUIBottomSheetDialogTest : SysuiTestCase() { @Test fun onConfigurationChanged_calledInDelegate() { - kosmos.testScope.runTest { + kosmos.runTest { dialog.show() - runCurrent() val captor = argumentCaptor<ConfigurationController.ConfigurationListener>() verify(configurationController).addCallback(capture(captor)) captor.value.onConfigChanged(config) - runCurrent() verify(delegate).onConfigurationChanged(any(), any()) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt index 06b3b57bd133..b2378d2c3aae 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.kosmos.mainCoroutineContext import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.activityStarter import com.android.systemui.runOnMainThreadAndWaitForIdleSync +import com.android.systemui.shade.data.repository.shadeDialogContextInteractor import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.systemUIDialogFactory import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.modesDialogViewModel @@ -78,6 +79,7 @@ class ModesDialogDelegateTest : SysuiTestCase() { { kosmos.modesDialogViewModel }, mockDialogEventLogger, kosmos.mainCoroutineContext, + kosmos.shadeDialogContextInteractor, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index 7b52dd836b51..5cd0846ded7e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -501,6 +501,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { } @Test + @DisableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI) public void onWallpaperColorsChanged_changeLockWallpaper() { // Should ask for a new theme when wallpaper colors change WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), diff --git a/packages/SystemUI/res/color/slider_active_track_color.xml b/packages/SystemUI/res/color/slider_active_track_color.xml deleted file mode 100644 index 8ba5e4901a7a..000000000000 --- a/packages/SystemUI/res/color/slider_active_track_color.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - ~ Copyright (C) 2024 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> - <item android:color="@androidprv:color/materialColorPrimary" android:state_enabled="true" /> - <item android:color="@androidprv:color/materialColorSurfaceContainerHighest" /> -</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/color/slider_inactive_track_color.xml b/packages/SystemUI/res/color/slider_inactive_track_color.xml deleted file mode 100644 index 7980f804a516..000000000000 --- a/packages/SystemUI/res/color/slider_inactive_track_color.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - ~ Copyright (C) 2024 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> - <item android:color="@androidprv:color/materialColorSurfaceContainerHighest" android:state_enabled="true" /> - <item android:color="@androidprv:color/materialColorPrimary" /> -</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/color/slider_thumb_color.xml b/packages/SystemUI/res/color/slider_thumb_color.xml deleted file mode 100644 index 8a98902426f8..000000000000 --- a/packages/SystemUI/res/color/slider_thumb_color.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - ~ Copyright (C) 2024 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> - -<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> - <item android:color="@androidprv:color/materialColorSurfaceContainerHighest" android:state_enabled="false" /> - <item android:color="@androidprv:color/materialColorPrimary" /> -</selector> diff --git a/packages/SystemUI/res/drawable/audio_bars_idle.xml b/packages/SystemUI/res/drawable/audio_bars_idle.xml new file mode 100644 index 000000000000..92a24755fece --- /dev/null +++ b/packages/SystemUI/res/drawable/audio_bars_idle.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="utf-8"?><!-- 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="168dp" + android:height="168dp" + android:viewportWidth="168" + android:viewportHeight="168"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_2_G" + android:translateX="121.161" + android:translateY="83.911"> + <path + android:name="_R_G_L_2_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M-37.16 -5.87 C-33.94,-5.87 -31.32,-3.32 -31.2,-0.13 C-31.2,-0.06 -31.2,0.02 -31.2,0.09 C-31.2,0.16 -31.2,0.23 -31.2,0.29 C-31.31,3.49 -33.94,6.05 -37.16,6.05 C-40.39,6.05 -43.01,3.49 -43.12,0.29 C-43.12,0.23 -43.12,0.16 -43.12,0.09 C-43.12,0.01 -43.12,-0.07 -43.12,-0.15 C-42.99,-3.33 -40.37,-5.87 -37.16,-5.87c " /> + </group> + <group + android:name="_R_G_L_1_G" + android:translateX="102.911" + android:translateY="83.911"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M-37.16 -5.87 C-33.94,-5.87 -31.32,-3.32 -31.2,-0.13 C-31.2,-0.06 -31.2,0.02 -31.2,0.09 C-31.2,0.16 -31.2,0.23 -31.2,0.29 C-31.31,3.49 -33.94,6.05 -37.16,6.05 C-40.39,6.05 -43.01,3.49 -43.12,0.29 C-43.12,0.23 -43.12,0.16 -43.12,0.09 C-43.12,0.01 -43.12,-0.07 -43.12,-0.15 C-42.99,-3.33 -40.37,-5.87 -37.16,-5.87c " /> + </group> + <group + android:name="_R_G_L_0_G" + android:translateX="139.661" + android:translateY="83.911"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M-37.16 -5.87 C-33.94,-5.87 -31.32,-3.32 -31.2,-0.13 C-31.2,-0.06 -31.2,0.02 -31.2,0.09 C-31.2,0.16 -31.2,0.23 -31.2,0.29 C-31.31,3.49 -33.94,6.05 -37.16,6.05 C-40.39,6.05 -43.01,3.49 -43.12,0.29 C-43.12,0.23 -43.12,0.16 -43.12,0.09 C-43.12,0.01 -43.12,-0.07 -43.12,-0.15 C-42.99,-3.33 -40.37,-5.87 -37.16,-5.87c " /> + </group> + </group> + <group android:name="time_group" /> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml index a3bad8f012ac..5ccedeafcb59 100644 --- a/packages/SystemUI/res/layout/volume_dialog.xml +++ b/packages/SystemUI/res/layout/volume_dialog.xml @@ -56,8 +56,8 @@ android:layout_marginTop="@dimen/volume_dialog_components_spacing" android:background="@drawable/ripple_drawable_20dp" android:contentDescription="@string/accessibility_volume_settings" + android:scaleType="centerInside" android:soundEffectsEnabled="false" - android:src="@drawable/horizontal_ellipsis" android:tint="@androidprv:color/materialColorPrimary" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container" diff --git a/packages/SystemUI/res/layout/volume_dialog_slider.xml b/packages/SystemUI/res/layout/volume_dialog_slider.xml index 9ac456c17084..0acf4109bbb5 100644 --- a/packages/SystemUI/res/layout/volume_dialog_slider.xml +++ b/packages/SystemUI/res/layout/volume_dialog_slider.xml @@ -14,7 +14,6 @@ limitations under the License. --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="wrap_content" android:layout_height="wrap_content"> @@ -24,11 +23,6 @@ android:layout_width="@dimen/volume_dialog_slider_width" android:layout_height="@dimen/volume_dialog_slider_height" android:layout_gravity="center" - android:theme="@style/Theme.Material3.Light" android:orientation="vertical" - app:thumbHeight="52dp" - app:trackCornerSize="12dp" - app:trackHeight="40dp" - app:trackStopIndicatorSize="6dp" - app:trackInsideCornerSize="2dp" /> + android:theme="@style/Theme.Material3.DayNight" /> </FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/raw/audio_bars_in.json b/packages/SystemUI/res/raw/audio_bars_in.json new file mode 100644 index 000000000000..c90a59c47d64 --- /dev/null +++ b/packages/SystemUI/res/raw/audio_bars_in.json @@ -0,0 +1 @@ +{"v":"5.7.13","fr":60,"ip":0,"op":18,"w":168,"h":168,"nm":"audio_bars_in","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":5,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[120.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":5,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[102.5,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[65.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[84,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":5,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[47.25,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":5,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file diff --git a/packages/SystemUI/res/raw/audio_bars_out.json b/packages/SystemUI/res/raw/audio_bars_out.json new file mode 100644 index 000000000000..5eab65e057ab --- /dev/null +++ b/packages/SystemUI/res/raw/audio_bars_out.json @@ -0,0 +1 @@ +{"v":"5.7.13","fr":60,"ip":0,"op":31,"w":168,"h":168,"nm":"audio_bars_out","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[120.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[102.5,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[65.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[84,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[47.25,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file diff --git a/packages/SystemUI/res/raw/audio_bars_playing.json b/packages/SystemUI/res/raw/audio_bars_playing.json new file mode 100644 index 000000000000..6ee8e1915f36 --- /dev/null +++ b/packages/SystemUI/res/raw/audio_bars_playing.json @@ -0,0 +1 @@ +{"v":"5.7.13","fr":60,"ip":0,"op":121,"w":168,"h":168,"nm":"audio_bars_playing","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[120.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":38,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[-0.016,-14.1],[5.941,-8.358],[5.961,0],[5.958,8.516],[0,14.274],[-5.958,8.515],[-5.961,0],[-5.972,-8.373]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":70,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":102,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[-0.016,-14.1],[5.941,-8.358],[5.961,0],[5.958,8.516],[0,14.274],[-5.958,8.515],[-5.961,0],[-5.972,-8.373]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[102.5,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":32,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-19.1],[5.957,-13.358],[5.961,0],[5.958,13.641],[0,19.399],[-5.958,13.64],[-5.961,0],[-5.957,-13.373]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":65,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":97,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-19.1],[5.957,-13.358],[5.961,0],[5.958,13.641],[0,19.399],[-5.958,13.64],[-5.961,0],[-5.957,-13.373]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[65.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":29,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-15.85],[5.957,-10.108],[5.961,0],[5.958,10.516],[0,16.274],[-5.958,10.515],[-5.961,0],[-5.957,-10.123]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":59,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":91,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-15.85],[5.957,-10.108],[5.961,0],[5.958,10.516],[0,16.274],[-5.958,10.515],[-5.961,0],[-5.957,-10.123]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[84,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":24,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-9.225],[5.957,-3.483],[5.961,0],[5.958,3.766],[0,9.524],[-5.958,3.765],[-5.961,0],[-5.957,-3.498]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":54,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":86,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-9.225],[5.957,-3.483],[5.961,0],[5.958,3.766],[0,9.524],[-5.958,3.765],[-5.961,0],[-5.957,-3.498]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[47.25,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":19,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":48,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":81,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0}],"markers":[{"tm":60,"cm":"1","dr":0}]}
\ No newline at end of file diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml index c1eff5f629b3..a77f5e4629c1 100644 --- a/packages/SystemUI/res/values-night/colors.xml +++ b/packages/SystemUI/res/values-night/colors.xml @@ -108,6 +108,9 @@ <color name="people_tile_background">@color/material_dynamic_secondary20</color> + <!-- Dark Theme colors for notification shade/scrim --> + <color name="shade_panel">@android:color/system_accent1_900</color> + <!-- Keyboard shortcut helper dialog --> <color name="ksh_key_item_color">@*android:color/system_on_surface_variant_dark</color> </resources> diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml index b4383156dc71..ec24c3df36a8 100644 --- a/packages/SystemUI/res/values-sw600dp/config.xml +++ b/packages/SystemUI/res/values-sw600dp/config.xml @@ -19,10 +19,13 @@ <!-- These resources are around just to allow their values to be customized for different hardware and product builds. --> -<resources> +<resources xmlns:android="http://schemas.android.com/apk/res/android"> <!-- The maximum number of rows in the QuickSettings --> <integer name="quick_settings_max_rows">4</integer> + <!-- The number of columns in the Split Shade QuickSettings --> + <integer name="quick_settings_split_shade_num_columns">6</integer> + <!-- Use collapsed layout for media player in landscape QQS --> <bool name="config_quickSettingsMediaLandscapeCollapsed">false</bool> @@ -51,7 +54,9 @@ ignored. --> <string-array name="config_keyguardQuickAffordanceDefaults" translatable="false"> <item>bottom_start:home</item> - <item>bottom_end:create_note</item> + <!-- TODO(b/384119565): revisit decision on defaults --> + <item android:featureFlag="!com.android.systemui.glanceable_hub_v2_resources">bottom_end:create_note</item> + <item android:featureFlag="com.android.systemui.glanceable_hub_v2_resources">bottom_end:glanceable_hub</item> </string-array> <!-- Whether volume panel should use the large screen layout or not --> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 28df2e2a1b8c..d2b7d0b90c43 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -31,6 +31,9 @@ <!-- The dark background color behind the shade --> <color name="shade_scrim_background_dark">@androidprv:color/system_under_surface_light</color> + <!-- Colors for notification shade/scrim --> + <color name="shade_panel">@android:color/system_accent1_800</color> + <!-- The color of the background in the separated list of the Global Actions menu --> <color name="global_actions_separated_background">#F5F5F5</color> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 56aaf4c0c564..d3ee63ba0dd1 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2504,14 +2504,14 @@ <!-- Accessibility description of action to remove QS tile on click. It will read as "Double-tap to remove tile" in screen readers [CHAR LIMIT=NONE] --> <string name="accessibility_qs_edit_remove_tile_action">remove tile</string> - <!-- Accessibility action of action to add QS tile to end. It will read as "Double-tap to add tile to end" in screen readers [CHAR LIMIT=NONE] --> - <string name="accessibility_qs_edit_tile_add_action">add tile to end</string> + <!-- Accessibility action of action to add QS tile to end. It will read as "Double-tap to add tile to the last position" in screen readers [CHAR LIMIT=NONE] --> + <string name="accessibility_qs_edit_tile_add_action">add tile to the last position</string> <!-- Accessibility action for context menu to move QS tile [CHAR LIMIT=NONE] --> <string name="accessibility_qs_edit_tile_start_move">Move tile</string> - <!-- Accessibility action for context menu to add QS tile [CHAR LIMIT=NONE] --> - <string name="accessibility_qs_edit_tile_start_add">Add tile</string> + <!-- Accessibility action for context menu to add QS tile to a position [CHAR LIMIT=NONE] --> + <string name="accessibility_qs_edit_tile_start_add">Add tile to desired position</string> <!-- Accessibility description when QS tile is to be moved, indicating the destination position [CHAR LIMIT=NONE] --> <string name="accessibility_qs_edit_tile_move_to_position">Move to <xliff:g id="position" example="5">%1$d</xliff:g></string> @@ -2564,7 +2564,7 @@ <string name="accessibility_quick_settings_open_settings">Open <xliff:g name="page" example="Bluetooth">%s</xliff:g> settings.</string> <!-- accessibility label for button to edit quick settings [CHAR LIMIT=NONE] --> - <string name="accessibility_quick_settings_edit">Edit order of settings.</string> + <string name="accessibility_quick_settings_edit">Edit order of Quick Settings.</string> <!-- accessibility label for button to open power menu [CHAR LIMIT=NONE] --> <string name="accessibility_quick_settings_power_menu">Power menu</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 3156a50df96f..f6c1ecea2886 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -559,15 +559,18 @@ <style name="SystemUI.Material3.Slider.Volume"> <item name="trackHeight">40dp</item> <item name="thumbHeight">52dp</item> + <item name="trackCornerSize">12dp</item> + <item name="trackInsideCornerSize">2dp</item> + <item name="trackStopIndicatorSize">6dp</item> </style> <style name="SystemUI.Material3.Slider" parent="@style/Widget.Material3.Slider"> <item name="labelStyle">@style/Widget.Material3.Slider.Label</item> - <item name="thumbColor">@color/slider_thumb_color</item> - <item name="tickColorActive">@color/slider_inactive_track_color</item> - <item name="tickColorInactive">@color/slider_active_track_color</item> - <item name="trackColorActive">@color/slider_active_track_color</item> - <item name="trackColorInactive">@color/slider_inactive_track_color</item> + <item name="thumbColor">@androidprv:color/materialColorPrimary</item> + <item name="tickColorActive">@androidprv:color/materialColorSurfaceContainerHighest</item> + <item name="tickColorInactive">@androidprv:color/materialColorPrimary</item> + <item name="trackColorActive">@androidprv:color/materialColorPrimary</item> + <item name="trackColorInactive">@androidprv:color/materialColorSurfaceContainerHighest</item> </style> <style name="Theme.SystemUI.DayNightDialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"/> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index fc536bdb126b..6f13d637d5c5 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -20,6 +20,7 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; +import static com.android.systemui.Flags.glanceableHubBackAction; import static com.android.systemui.shared.Flags.shadeAllowBackGesture; import android.annotation.LongDef; @@ -352,6 +353,10 @@ public class QuickStepContract { } // Disable back gesture on the hub, but not when the shade is showing. if ((sysuiStateFlags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) { + // Allow back gesture on Glanceable Hub with back action support. + if (glanceableHubBackAction()) { + return false; + } // Use QS expanded signal as the notification panel is always considered visible // expanded when on the lock screen and when opening hub over lock screen. This does // mean that back gesture is disabled when opening shade over hub while in portrait diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 5af80cbd4b29..71b622aa0608 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -43,7 +43,6 @@ import com.android.systemui.dagger.qualifiers.DisplaySpecific import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags.REGION_SAMPLING -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge @@ -85,8 +84,8 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge /** - * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by - * [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController]. + * Controller for a Clock provided by the registry and used on the keyguard. Functionality is forked + * from [AnimatableClockController]. */ open class ClockEventController @Inject @@ -348,14 +347,6 @@ constructor( object : KeyguardUpdateMonitorCallback() { override fun onKeyguardVisibilityChanged(visible: Boolean) { isKeyguardVisible = visible - if (!MigrateClocksToBlueprint.isEnabled) { - if (!isKeyguardVisible) { - clock?.run { - smallClock.animations.doze(if (isDozing) 1f else 0f) - largeClock.animations.doze(if (isDozing) 1f else 0f) - } - } - } if (visible) { refreshTime() @@ -388,10 +379,6 @@ constructor( } private fun refreshTime() { - if (!MigrateClocksToBlueprint.isEnabled) { - return - } - clock?.smallClock?.events?.onTimeTick() clock?.largeClock?.events?.onTimeTick() } @@ -483,14 +470,10 @@ constructor( if (ModesUi.isEnabled) { listenForDnd(this) } - if (MigrateClocksToBlueprint.isEnabled) { - listenForDozeAmountTransition(this) - listenForAnyStateToAodTransition(this) - listenForAnyStateToLockscreenTransition(this) - listenForAnyStateToDozingTransition(this) - } else { - listenForDozeAmount(this) - } + listenForDozeAmountTransition(this) + listenForAnyStateToAodTransition(this) + listenForAnyStateToLockscreenTransition(this) + listenForAnyStateToDozingTransition(this) } } smallTimeListener?.update(shouldTimeListenerRun) @@ -596,11 +579,6 @@ constructor( } @VisibleForTesting - internal fun listenForDozeAmount(scope: CoroutineScope): Job { - return scope.launch { keyguardInteractor.dozeAmount.collect { handleDoze(it) } } - } - - @VisibleForTesting internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job { return scope.launch { merge( @@ -695,8 +673,7 @@ constructor( isRunning = true when (clockFace.config.tickRate) { ClockTickRate.PER_MINUTE -> { - // Handled by KeyguardClockSwitchController and - // by KeyguardUpdateMonitorCallback#onTimeChanged. + // Handled by KeyguardUpdateMonitorCallback#onTimeChanged. } ClockTickRate.PER_SECOND -> executor.execute(secondsRunnable) ClockTickRate.PER_FRAME -> { diff --git a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt index df77a58c3b34..3f332f769c6e 100644 --- a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt +++ b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt @@ -23,14 +23,11 @@ import android.graphics.Rect import android.os.Bundle import android.view.Display import android.view.Gravity -import android.view.LayoutInflater import android.view.View import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.WindowManager import android.widget.FrameLayout import android.widget.FrameLayout.LayoutParams -import com.android.keyguard.dagger.KeyguardStatusViewComponent -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.plugins.clocks.ClockFaceController import com.android.systemui.res.R @@ -45,7 +42,6 @@ class ConnectedDisplayKeyguardPresentation constructor( @Assisted display: Display, context: Context, - private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory, private val clockRegistry: ClockRegistry, private val clockEventController: ClockEventController, ) : @@ -53,12 +49,11 @@ constructor( context, display, R.style.Theme_SystemUI_KeyguardPresentation, - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, ) { private lateinit var rootView: FrameLayout private var clock: View? = null - private lateinit var keyguardStatusViewController: KeyguardStatusViewController private lateinit var faceController: ClockFaceController private lateinit var clockFrame: FrameLayout @@ -82,7 +77,7 @@ constructor( oldLeft: Int, oldTop: Int, oldRight: Int, - oldBottom: Int + oldBottom: Int, ) { clock?.let { faceController.events.onTargetRegionChanged( @@ -95,11 +90,7 @@ constructor( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - if (MigrateClocksToBlueprint.isEnabled) { - onCreateV2() - } else { - onCreate() - } + onCreateV2() } fun onCreateV2() { @@ -112,39 +103,15 @@ constructor( setClock(clockRegistry.createCurrentClock()) } - fun onCreate() { - setContentView( - LayoutInflater.from(context) - .inflate(R.layout.keyguard_clock_presentation, /* root= */ null) - ) - - setFullscreen() - - clock = requireViewById(R.id.clock) - keyguardStatusViewController = - keyguardStatusViewComponentFactory - .build(clock as KeyguardStatusView, display) - .keyguardStatusViewController - .apply { - setDisplayedOnSecondaryDisplay() - init() - } - } - override fun onAttachedToWindow() { - if (MigrateClocksToBlueprint.isEnabled) { - clockRegistry.registerClockChangeListener(clockChangedListener) - clockEventController.registerListeners(clock!!) - - faceController.animations.enter() - } + clockRegistry.registerClockChangeListener(clockChangedListener) + clockEventController.registerListeners(clock!!) + faceController.animations.enter() } override fun onDetachedFromWindow() { - if (MigrateClocksToBlueprint.isEnabled) { - clockEventController.unregisterListeners() - clockRegistry.unregisterClockChangeListener(clockChangedListener) - } + clockEventController.unregisterListeners() + clockRegistry.unregisterClockChangeListener(clockChangedListener) super.onDetachedFromWindow() } @@ -166,7 +133,7 @@ constructor( context.resources.getDimensionPixelSize(R.dimen.keyguard_presentation_width), WRAP_CONTENT, Gravity.CENTER, - ) + ), ) clockEventController.clock = clockController @@ -190,8 +157,6 @@ constructor( @AssistedFactory interface Factory { /** Creates a new [Presentation] for the given [display]. */ - fun create( - display: Display, - ): ConnectedDisplayKeyguardPresentation + fun create(display: Display): ConnectedDisplayKeyguardPresentation } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java index 1083136b570a..acfa08643b63 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java @@ -26,11 +26,9 @@ import android.hardware.display.DisplayManager; import android.media.MediaRouter; import android.media.MediaRouter.RouteInfo; import android.os.Trace; -import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; import android.view.Display; -import android.view.DisplayAddress; import android.view.DisplayInfo; import android.view.View; import android.view.WindowManager; @@ -58,6 +56,9 @@ import java.util.concurrent.Executor; import javax.inject.Inject; import javax.inject.Provider; +/** + * Manages Keyguard Presentations for non-primary display(s). + */ @SysUISingleton public class KeyguardDisplayManager { protected static final String TAG = "KeyguardDisplayManager"; @@ -170,14 +171,17 @@ public class KeyguardDisplayManager { } return false; } - if (mKeyguardStateController.isOccluded() - && mDeviceStateHelper.isConcurrentDisplayActive(display)) { + + final boolean deviceStateOccludesKeyguard = + mDeviceStateHelper.isConcurrentDisplayActive(display) + || mDeviceStateHelper.isRearDisplayOuterDefaultActive(display); + if (mKeyguardStateController.isOccluded() && deviceStateOccludesKeyguard) { if (DEBUG) { // When activities with FLAG_SHOW_WHEN_LOCKED are shown on top of Keyguard, the // Keyguard state becomes "occluded". In this case, we should not show the // KeyguardPresentation, since the activity is presenting content onto the // non-default display. - Log.i(TAG, "Do not show KeyguardPresentation when occluded and concurrent" + Log.i(TAG, "Do not show KeyguardPresentation when occluded and concurrent or rear" + " display is active"); } return false; @@ -326,44 +330,45 @@ public class KeyguardDisplayManager { public static class DeviceStateHelper implements DeviceStateManager.DeviceStateCallback { @Nullable - private final DisplayAddress.Physical mRearDisplayPhysicalAddress; - - // TODO(b/271317597): These device states should be defined in DeviceStateManager - private final int mConcurrentState; - private boolean mIsInConcurrentDisplayState; + private DeviceState mDeviceState; @Inject DeviceStateHelper( - @ShadeDisplayAware Context context, DeviceStateManager deviceStateManager, @Main Executor mainExecutor) { - - final String rearDisplayPhysicalAddress = context.getResources().getString( - com.android.internal.R.string.config_rearDisplayPhysicalAddress); - if (TextUtils.isEmpty(rearDisplayPhysicalAddress)) { - mRearDisplayPhysicalAddress = null; - } else { - mRearDisplayPhysicalAddress = DisplayAddress - .fromPhysicalDisplayId(Long.parseLong(rearDisplayPhysicalAddress)); - } - - mConcurrentState = context.getResources().getInteger( - com.android.internal.R.integer.config_deviceStateConcurrentRearDisplay); deviceStateManager.registerCallback(mainExecutor, this); } @Override 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.getIdentifier() == mConcurrentState; + // When dual display or rear display mode 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. + mDeviceState = state; + } + + /** + * @return true if the device is in Dual Display mode, and the specified display is the + * rear facing (outer) display. + */ + boolean isConcurrentDisplayActive(@NonNull Display display) { + return mDeviceState != null + && mDeviceState.hasProperty( + DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT) + && (display.getFlags() & Display.FLAG_REAR) != 0; } - boolean isConcurrentDisplayActive(Display display) { - return mIsInConcurrentDisplayState - && mRearDisplayPhysicalAddress != null - && mRearDisplayPhysicalAddress.equals(display.getAddress()); + /** + * @return true if the device is the updated Rear Display mode, and the specified display is + * the inner display. See {@link DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT}. + * Note that in this state, the outer display is the default display, while the inner + * display is the "rear" display. + */ + boolean isRearDisplayOuterDefaultActive(@NonNull Display display) { + return mDeviceState != null + && mDeviceState.hasProperty( + DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT) + && (display.getFlags() & Display.FLAG_REAR) != 0; } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt index 07bd813c2420..40a86dc3713e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt @@ -19,13 +19,12 @@ package com.android.keyguard import android.content.Context import android.view.View import com.android.systemui.customization.R as customR -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R -import com.android.systemui.shared.R as sharedR import com.android.systemui.shade.NotificationShadeWindowView import com.android.systemui.shade.ShadeDisplayAware +import com.android.systemui.shared.R as sharedR import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START @@ -55,16 +54,17 @@ constructor( var statusViewCentered = false private val filterKeyguardAndSplitShadeOnly: () -> Boolean = { - statusBarStateController.getState() == KEYGUARD && !statusViewCentered } + statusBarStateController.getState() == KEYGUARD && !statusViewCentered + } private val filterKeyguard: () -> Boolean = { statusBarStateController.getState() == KEYGUARD } private val translateAnimator by lazy { - val smartSpaceViews = if (MigrateClocksToBlueprint.isEnabled) { - // Use scrollX instead of translationX as translation is already set by [AodBurnInLayer] - val scrollXTranslation = { view: View, translation: Float -> - view.scrollX = -translation.toInt() - } + // Use scrollX instead of translationX as translation is already set by [AodBurnInLayer] + val scrollXTranslation = { view: View, translation: Float -> + view.scrollX = -translation.toInt() + } + val smartSpaceViews = setOf( ViewIdToTranslate( viewId = sharedR.id.date_smartspace_view, @@ -83,18 +83,8 @@ constructor( direction = START, shouldBeAnimated = filterKeyguard, translateFunc = scrollXTranslation, - ) + ), ) - } else { - setOf(ViewIdToTranslate( - viewId = R.id.keyguard_status_area, - direction = START, - shouldBeAnimated = filterKeyguard, - translateFunc = { view, value -> - (view as? KeyguardStatusAreaView)?.translateXFromUnfold = value - } - )) - } UnfoldConstantTranslateAnimator( viewsIdToTranslate = @@ -102,39 +92,39 @@ constructor( ViewIdToTranslate( viewId = customR.id.lockscreen_clock_view_large, direction = START, - shouldBeAnimated = filterKeyguardAndSplitShadeOnly + shouldBeAnimated = filterKeyguardAndSplitShadeOnly, ), ViewIdToTranslate( viewId = customR.id.lockscreen_clock_view, direction = START, - shouldBeAnimated = filterKeyguard + shouldBeAnimated = filterKeyguard, ), ViewIdToTranslate( viewId = R.id.notification_stack_scroller, direction = END, - shouldBeAnimated = filterKeyguardAndSplitShadeOnly - ) + shouldBeAnimated = filterKeyguardAndSplitShadeOnly, + ), ) + smartSpaceViews, - progressProvider = unfoldProgressProvider + progressProvider = unfoldProgressProvider, ) } private val shortcutButtonsAnimator by lazy { UnfoldConstantTranslateAnimator( viewsIdToTranslate = - setOf( - ViewIdToTranslate( - viewId = R.id.start_button, - direction = START, - shouldBeAnimated = filterKeyguard + setOf( + ViewIdToTranslate( + viewId = R.id.start_button, + direction = START, + shouldBeAnimated = filterKeyguard, + ), + ViewIdToTranslate( + viewId = R.id.end_button, + direction = END, + shouldBeAnimated = filterKeyguard, + ), ), - ViewIdToTranslate( - viewId = R.id.end_button, - direction = END, - shouldBeAnimated = filterKeyguard - ) - ), - progressProvider = unfoldProgressProvider + progressProvider = unfoldProgressProvider, ) } diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java index 0305b5e5ab63..e76f38c8c75c 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java @@ -26,7 +26,6 @@ import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; -import com.android.systemui.keyguard.MigrateClocksToBlueprint; import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.clocks.ClockMessageBuffers; import com.android.systemui.res.R; @@ -70,7 +69,7 @@ public abstract class ClockRegistryModule { context, layoutInflater, resources, - MigrateClocksToBlueprint.isEnabled(), + com.android.systemui.Flags.clockReactiveVariants() ), context.getString(R.string.lockscreen_clock_id_fallback), diff --git a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt index 232b62985ad0..47910f3d25bc 100644 --- a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt @@ -21,8 +21,11 @@ import android.window.OnBackAnimationCallback import android.window.OnBackInvokedCallback import android.window.OnBackInvokedDispatcher import android.window.WindowOnBackInvokedDispatcher +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.CoreStartable +import com.android.systemui.Flags.glanceableHubBackAction import com.android.systemui.Flags.predictiveBackAnimateShade +import com.android.systemui.communal.domain.interactor.CommunalBackActionInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -35,7 +38,6 @@ import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import javax.inject.Inject import kotlinx.coroutines.CoroutineScope -import com.android.app.tracing.coroutines.launchTraced as launch /** Handles requests to go back either from a button or gesture. */ @SysUISingleton @@ -50,6 +52,7 @@ constructor( private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor, private val shadeBackActionInteractor: ShadeBackActionInteractor, private val qsController: QuickSettingsController, + private val communalBackActionInteractor: CommunalBackActionInteractor, ) : CoreStartable { private var isCallbackRegistered = false @@ -114,6 +117,12 @@ constructor( if (shadeBackActionInteractor.closeUserSwitcherIfOpen()) { return true } + if (glanceableHubBackAction()) { + if (communalBackActionInteractor.canBeDismissed()) { + communalBackActionInteractor.onBackPressed() + return true + } + } if (shouldBackBeHandled()) { if (shadeBackActionInteractor.canBeCollapsed()) { // this is the Shade dismiss animation, so make sure QQS closes when it ends. diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt index 4c2dc41fb759..d8c628fd680b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt @@ -155,6 +155,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at override fun onAnimationEnd(animation: Animator) { drawDwell = false resetDwellAlpha() + invalidate() } }) start() @@ -191,6 +192,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at override fun onAnimationEnd(animation: Animator) { drawDwell = false resetDwellAlpha() + invalidate() } }) start() @@ -248,6 +250,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at override fun onAnimationEnd(animation: Animator) { drawDwell = false + invalidate() } }) start() diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt index 9cfb5be478ed..b294dd1b0b71 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt @@ -41,6 +41,7 @@ import com.android.internal.R as InternalR import com.android.internal.logging.UiEventLogger import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.res.R +import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.util.time.SystemClock import dagger.assisted.Assisted @@ -68,6 +69,7 @@ internal constructor( private val uiEventLogger: UiEventLogger, private val logger: BluetoothTileDialogLogger, private val systemuiDialogFactory: SystemUIDialog.Factory, + private val shadeDialogContextInteractor: ShadeDialogContextInteractor, ) : SystemUIDialog.Delegate { private val mutableBluetoothStateToggle: MutableStateFlow<Boolean?> = MutableStateFlow(null) @@ -105,7 +107,7 @@ internal constructor( } override fun createDialog(): SystemUIDialog { - return systemuiDialogFactory.create(this) + return systemuiDialogFactory.create(this, shadeDialogContextInteractor.context) } override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { @@ -405,10 +407,11 @@ internal constructor( } // updating icon colors - val tintColor = context.getColor( - if (item.isActive) InternalR.color.materialColorOnPrimaryContainer - else InternalR.color.materialColorOnSurface - ) + val tintColor = + context.getColor( + if (item.isActive) InternalR.color.materialColorOnPrimaryContainer + else InternalR.color.materialColorOnSurface + ) // update icons iconView.apply { diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt index 26abb48ce7db..73c0179cf8ec 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt @@ -55,6 +55,8 @@ interface CommunalSettingsRepository { /** A [CommunalEnabledState] for the specified user. */ fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState> + fun getScreensaverEnabledState(user: UserInfo): Flow<Boolean> + /** * Returns true if any glanceable hub functionality should be enabled via configs and flags. * @@ -138,6 +140,20 @@ constructor( .flowOn(bgDispatcher) } + override fun getScreensaverEnabledState(user: UserInfo): Flow<Boolean> = + secureSettings + .observerFlow(userId = user.id, names = arrayOf(Settings.Secure.SCREENSAVER_ENABLED)) + // Force an update + .onStart { emit(Unit) } + .map { + secureSettings.getIntForUser( + Settings.Secure.SCREENSAVER_ENABLED, + SCREENSAVER_ENABLED_SETTING_DEFAULT, + user.id, + ) == 1 + } + .flowOn(bgDispatcher) + override fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> = broadcastDispatcher .broadcastFlow( @@ -182,6 +198,7 @@ constructor( companion object { const val GLANCEABLE_HUB_BACKGROUND_SETTING = "glanceable_hub_background" private const val ENABLED_SETTING_DEFAULT = 1 + private const val SCREENSAVER_ENABLED_SETTING_DEFAULT = 0 } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractor.kt new file mode 100644 index 000000000000..2ccf96abff79 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractor.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.domain.interactor + +import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes +import javax.inject.Inject + +/** + * {@link CommunalBackActionInteractor} is responsible for handling back gestures on the glanceable + * hub. When invoked SystemUI should navigate back to the lockscreen. + */ +@SysUISingleton +class CommunalBackActionInteractor +@Inject +constructor( + private val communalInteractor: CommunalInteractor, + private val communalSceneInteractor: CommunalSceneInteractor, + private val sceneInteractor: SceneInteractor, +) { + fun canBeDismissed(): Boolean { + return communalInteractor.isCommunalShowing.value + } + + fun onBackPressed() { + if (SceneContainerFlag.isEnabled) { + // TODO(b/384610333): Properly determine whether to go to dream or lockscreen on back. + sceneInteractor.changeScene( + toScene = Scenes.Lockscreen, + loggingReason = "CommunalBackActionInteractor", + ) + } else { + communalSceneInteractor.changeScene( + newScene = CommunalScenes.Blank, + loggingReason = "CommunalBackActionInteractor", + ) + } + } +} 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 ea428698e476..947113da0e60 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 @@ -285,7 +285,7 @@ constructor( * use [isIdleOnCommunal]. */ // TODO(b/323215860): rename to something more appropriate after cleaning up usages - val isCommunalShowing: Flow<Boolean> = + val isCommunalShowing: StateFlow<Boolean> = flow { emit(SceneContainerFlag.isEnabled) } .flatMapLatest { sceneContainerEnabled -> if (sceneContainerEnabled) { @@ -304,10 +304,10 @@ constructor( columnName = "isCommunalShowing", initialValue = false, ) - .shareIn( + .stateIn( scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - replay = 1, + started = SharingStarted.Eagerly, + initialValue = false, ) /** diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt index 862b05bc9b5d..c1f21e4046a3 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt @@ -69,6 +69,12 @@ constructor( // Start this eagerly since the value is accessed synchronously in many places. .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false) + /** Whether or not screensaver (dreams) is enabled for the currently selected user. */ + val isScreensaverEnabled: Flow<Boolean> = + userInteractor.selectedUserInfo.flatMapLatest { user -> + repository.getScreensaverEnabledState(user) + } + /** * Returns true if any glanceable hub functionality should be enabled via configs and flags. * diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt index 7d5b196dfaa8..c6f96e198b91 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt @@ -18,10 +18,15 @@ package com.android.systemui.communal.ui.viewmodel import android.annotation.SuppressLint import android.app.DreamManager +import android.content.Intent +import android.provider.Settings +import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.lifecycle.ExclusiveActivatable +import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.util.kotlin.isDevicePluggedIn +import com.android.systemui.util.kotlin.sample import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlin.coroutines.CoroutineContext @@ -31,7 +36,6 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -41,6 +45,8 @@ class CommunalToDreamButtonViewModel constructor( @Background private val backgroundContext: CoroutineContext, batteryController: BatteryController, + private val settingsInteractor: CommunalSettingsInteractor, + private val activityStarter: ActivityStarter, private val dreamManager: DreamManager, ) : ExclusiveActivatable() { @@ -49,11 +55,7 @@ constructor( /** Whether we should show a button on hub to switch to dream. */ @SuppressLint("MissingPermission") val shouldShowDreamButtonOnHub = - batteryController - .isDevicePluggedIn() - .distinctUntilChanged() - .map { isPluggedIn -> isPluggedIn && dreamManager.canStartDreaming(true) } - .flowOn(backgroundContext) + batteryController.isDevicePluggedIn().distinctUntilChanged().flowOn(backgroundContext) /** Handle a tap on the "show dream" button. */ fun onShowDreamButtonTap() { @@ -63,9 +65,21 @@ constructor( @SuppressLint("MissingPermission") override suspend fun onActivated(): Nothing = coroutineScope { launch { - _requests.receiveAsFlow().collectLatest { - withContext(backgroundContext) { dreamManager.startDream() } - } + _requests + .receiveAsFlow() + .sample(settingsInteractor.isScreensaverEnabled) + .collectLatest { enabled -> + withContext(backgroundContext) { + if (enabled) { + dreamManager.startDream() + } else { + activityStarter.postStartActivityDismissingKeyguard( + Intent(Settings.ACTION_DREAM_SETTINGS), + 0, + ) + } + } + } } awaitCancellation() diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index 9ae106c3ab39..014c0db618e1 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -267,6 +267,7 @@ public class FrameworkServicesModule { } @Provides + @Nullable @Singleton static VirtualDeviceManager provideVirtualDeviceManager(Context context) { return context.getSystemService(VirtualDeviceManager.class); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index 571b37f43fd4..b272d65a8a11 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -54,6 +54,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.policy.PhoneWindow; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.systemui.Flags; import com.android.systemui.ambient.touch.TouchHandler; import com.android.systemui.ambient.touch.TouchMonitor; import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent; @@ -210,6 +211,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ mCommunalVisible = communalVisible; updateLifecycleStateLocked(); + updateGestureBlockingLocked(); }); } }; @@ -585,7 +587,8 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ private void updateGestureBlockingLocked() { final boolean shouldBlock = mStarted && !mShadeExpanded && !mBouncerShowing - && !isDreamInPreviewMode(); + && !isDreamInPreviewMode() + && !(Flags.glanceableHubBackAction() && mCommunalVisible); if (shouldBlock) { mGestureInteractor.addGestureBlockedMatcher(DREAM_TYPE_MATCHER, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index f549e64ca853..d0065c8b06c6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -39,7 +39,6 @@ import androidx.annotation.VisibleForTesting import androidx.core.math.MathUtils import com.android.app.animation.Interpolators import com.android.internal.R -import com.android.keyguard.KeyguardClockSwitchController import com.android.keyguard.KeyguardViewController import com.android.systemui.Flags.fasterUnlockTransition import com.android.systemui.dagger.SysUISingleton @@ -206,7 +205,7 @@ constructor( fun onUnlockAnimationFinished() {} } - /** The SmartSpace view on the lockscreen, provided by [KeyguardClockSwitchController]. */ + /** The SmartSpace view on the lockscreen. */ var lockscreenSmartspace: View? = null /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 9f131607cb99..63ac5094c400 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -4058,7 +4058,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException { - mRunner = mActivityTransitionAnimator.get().createRunner(mActivityLaunchController); + mRunner = mActivityTransitionAnimator.get() + .createEphemeralRunner(mActivityLaunchController); mRunner.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback); } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt index 4bac8f7a1b47..a1fb1a7bb113 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt @@ -110,7 +110,7 @@ constructor( apps: Array<RemoteAnimationTarget>, wallpapers: Array<RemoteAnimationTarget>, nonApps: Array<RemoteAnimationTarget>, - finishedCallback: IRemoteAnimationFinishedCallback? + finishedCallback: IRemoteAnimationFinishedCallback?, ) { Log.d(TAG, "occludeAnimationRunner#onAnimationStart") // Wrap the callback so that it's guaranteed to be nulled out once called. @@ -126,7 +126,7 @@ constructor( taskInfo = apps.firstOrNull()?.taskInfo, ) activityTransitionAnimator - .createRunner(occludeAnimationController) + .createEphemeralRunner(occludeAnimationController) .onAnimationStart( transit, apps, @@ -161,7 +161,7 @@ constructor( apps: Array<RemoteAnimationTarget>, wallpapers: Array<RemoteAnimationTarget>, nonApps: Array<RemoteAnimationTarget>, - finishedCallback: IRemoteAnimationFinishedCallback? + finishedCallback: IRemoteAnimationFinishedCallback?, ) { Log.d(TAG, "unoccludeAnimationRunner#onAnimationStart") // Wrap the callback so that it's guaranteed to be nulled out once called. @@ -179,14 +179,14 @@ constructor( interactionJankMonitor.begin( createInteractionJankMonitorConf( InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION, - "UNOCCLUDE" + "UNOCCLUDE", ) ) if (apps.isEmpty()) { Log.d( TAG, "No apps provided to unocclude runner; " + - "skipping animation and unoccluding." + "skipping animation and unoccluding.", ) unoccludeAnimationFinishedCallback?.onAnimationFinished() return @@ -210,7 +210,7 @@ constructor( 0f, (1f - animatedValue) * surfaceHeight * - UNOCCLUDE_TRANSLATE_DISTANCE_PERCENT + UNOCCLUDE_TRANSLATE_DISTANCE_PERCENT, ) SurfaceParams.Builder(target.leash) @@ -313,12 +313,12 @@ constructor( private fun createInteractionJankMonitorConf( cuj: Int, - tag: String? + tag: String?, ): InteractionJankMonitor.Configuration.Builder { val builder = InteractionJankMonitor.Configuration.Builder.withView( cuj, - keyguardViewController.get().getViewRootImpl().view + keyguardViewController.get().getViewRootImpl().view, ) return if (tag != null) builder.setTag(tag) else builder } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt index 74ee052f12b9..57f06fbd3bb5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt @@ -21,13 +21,13 @@ import android.app.StatusBarManager import android.app.admin.DevicePolicyManager import android.content.Context import android.content.pm.PackageManager -import com.android.systemui.res.R import com.android.systemui.animation.Expandable import com.android.systemui.camera.CameraGestureHelper import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.shade.ShadeDisplayAware import dagger.Lazy @@ -65,7 +65,7 @@ constructor( icon = Icon.Resource( R.drawable.ic_camera, - ContentDescription.Resource(R.string.accessibility_camera_button) + ContentDescription.Resource(R.string.accessibility_camera_button), ) ) } else { @@ -88,7 +88,7 @@ constructor( cameraGestureHelper .get() .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE) - return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(true) } private suspend fun isLaunchable(): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt index e8d3bfac6361..1b8baf657948 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt @@ -210,16 +210,16 @@ constructor( ): KeyguardQuickAffordanceConfig.OnTriggeredResult { return if (ModesUi.isEnabled) { if (!isAvailable.value) { - KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false) } else { val dnd = interactor.dndMode.value if (dnd == null) { Log.wtf(TAG, "Triggered DND but it's null!?") - return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false) } if (dnd.isActive) { interactor.deactivateMode(dnd) - return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false) } else { if (interactor.shouldAskForZenDuration(dnd)) { // NOTE: The dialog handles turning on the mode itself. @@ -229,16 +229,16 @@ constructor( ) } else { interactor.activateMode(dnd) - return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false) } } } } else { when { - !oldIsAvailable -> KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + !oldIsAvailable -> KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false) zenMode != ZEN_MODE_OFF -> { controller.setZen(ZEN_MODE_OFF, null, TAG) - KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false) } settingsValue == ZEN_DURATION_PROMPT -> @@ -249,12 +249,12 @@ constructor( settingsValue == ZEN_DURATION_FOREVER -> { controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG) - KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false) } else -> { controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, conditionUri, TAG) - KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt index 480ef5e19d8e..e2642a0964c1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt @@ -18,15 +18,14 @@ package com.android.systemui.keyguard.data.quickaffordance import android.content.Context -import com.android.systemui.res.R import com.android.systemui.animation.Expandable import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.shared.quickaffordance.ActivationState +import com.android.systemui.res.R import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.policy.FlashlightController import javax.inject.Inject @@ -50,9 +49,9 @@ constructor( KeyguardQuickAffordanceConfig.LockScreenState.Visible( Icon.Resource( R.drawable.qs_flashlight_icon_on, - ContentDescription.Resource(R.string.quick_settings_flashlight_label) + ContentDescription.Resource(R.string.quick_settings_flashlight_label), ), - ActivationState.Active + ActivationState.Active, ) } @@ -61,9 +60,9 @@ constructor( KeyguardQuickAffordanceConfig.LockScreenState.Visible( Icon.Resource( R.drawable.qs_flashlight_icon_off, - ContentDescription.Resource(R.string.quick_settings_flashlight_label) + ContentDescription.Resource(R.string.quick_settings_flashlight_label), ), - ActivationState.Inactive + ActivationState.Inactive, ) } @@ -92,14 +91,14 @@ constructor( } else { FlashlightState.OffAvailable.toLockScreenState() }, - TAG + TAG, ) } override fun onFlashlightError() { trySendWithFailureLogging( FlashlightState.OffAvailable.toLockScreenState(), - TAG + TAG, ) } @@ -114,7 +113,7 @@ constructor( FlashlightState.OffAvailable.toLockScreenState() } }, - TAG + TAG, ) } } @@ -130,7 +129,7 @@ constructor( flashlightController.setFlashlight( flashlightController.isAvailable && !flashlightController.isEnabled ) - return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false) } override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt index d335a1806a6d..06da281648a7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt @@ -111,7 +111,7 @@ constructor( transitionKey = CommunalTransitionKeys.SimpleFade, ) } - return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(true) } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt index 1cf6183fec6c..ade65c38ff3c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt @@ -21,10 +21,10 @@ import android.app.AlertDialog import android.content.Context import android.content.Intent import android.net.Uri -import com.android.systemui.res.R import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.keyguard.shared.quickaffordance.ActivationState +import com.android.systemui.res.R import kotlinx.coroutines.flow.Flow /** Defines interface that can act as data source for a single quick affordance model. */ @@ -71,7 +71,7 @@ interface KeyguardQuickAffordanceConfig { /** The picker shows the item for selecting this affordance as it normally would. */ data class Default( /** Optional [Intent] to use to start an activity to configure this affordance. */ - val configureIntent: Intent? = null, + val configureIntent: Intent? = null ) : PickerScreenState() /** @@ -134,34 +134,39 @@ interface KeyguardQuickAffordanceConfig { ) : LockScreenState() } - sealed class OnTriggeredResult { + sealed class OnTriggeredResult() { /** * Returning this as a result from the [onTriggered] method means that the implementation * has taken care of the action, the system will do nothing. + * + * @param[actionLaunched] Whether the implementation handled the action by launching a + * dialog or an activity. */ - object Handled : OnTriggeredResult() + data class Handled(val actionLaunched: Boolean) : OnTriggeredResult() /** * Returning this as a result from the [onTriggered] method means that the implementation * has _not_ taken care of the action and the system should start an activity using the * given [Intent]. */ - data class StartActivity( - val intent: Intent, - val canShowWhileLocked: Boolean, - ) : OnTriggeredResult() + data class StartActivity(val intent: Intent, val canShowWhileLocked: Boolean) : + OnTriggeredResult() /** * Returning this as a result from the [onTriggered] method means that the implementation * has _not_ taken care of the action and the system should show a Dialog using the given * [AlertDialog] and [Expandable]. */ - data class ShowDialog( - val dialog: AlertDialog, - val expandable: Expandable?, - ) : OnTriggeredResult() + data class ShowDialog(val dialog: AlertDialog, val expandable: Expandable?) : + OnTriggeredResult() } + /** + * Models an [OnTriggeredResult] that did or did not launch a dialog or activity for a given + * config key. + */ + data class LaunchingFromTriggeredResult(val launched: Boolean, val configKey: String) + companion object { /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt index 1358634a55f8..1c9bc9f39663 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt @@ -21,6 +21,7 @@ import android.content.Context import android.media.AudioManager import androidx.lifecycle.LiveData import androidx.lifecycle.Observer +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.animation.Expandable import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.shared.model.ContentDescription @@ -45,7 +46,6 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart -import com.android.app.tracing.coroutines.launchTraced as launch import kotlinx.coroutines.withContext @SysUISingleton @@ -118,7 +118,7 @@ constructor( audioManager.ringerModeInternal = newRingerMode } } - return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false) } override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState = @@ -140,11 +140,11 @@ constructor( .getSharedPreferences( MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME, Context.MODE_PRIVATE, - userTracker.userId + userTracker.userId, ) .getInt( LAST_NON_SILENT_RINGER_MODE_KEY, - ringerModeTracker.ringerModeInternal.value ?: DEFAULT_LAST_NON_SILENT_VALUE + ringerModeTracker.ringerModeInternal.value ?: DEFAULT_LAST_NON_SILENT_VALUE, ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt index eafa1cea59f3..cb7702e090d0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt @@ -30,7 +30,6 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R @@ -72,21 +71,15 @@ constructor( override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) { val hasCards = getPaymentCards(response.walletCards)?.isNotEmpty() == true - trySendWithFailureLogging( - hasCards, - TAG, - ) + trySendWithFailureLogging(hasCards, TAG) } override fun onWalletCardRetrievalError(error: GetWalletCardsError) { Log.e( TAG, - "Wallet card retrieval error, message: \"${error?.message}\"" - ) - trySendWithFailureLogging( - null, - TAG, + "Wallet card retrieval error, message: \"${error?.message}\"", ) + trySendWithFailureLogging(null, TAG) } } @@ -94,7 +87,7 @@ constructor( callback, QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE, QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE, - QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE + QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE, ) withContext(backgroundDispatcher) { @@ -107,7 +100,7 @@ constructor( walletController.unregisterWalletChangeObservers( QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE, QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE, - QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE + QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE, ) } } @@ -117,11 +110,7 @@ constructor( if (hasCards == null) { KeyguardQuickAffordanceConfig.LockScreenState.Hidden } else { - state( - isWalletAvailable(), - hasCards, - walletController.walletClient.tileIcon, - ) + state(isWalletAvailable(), hasCards, walletController.walletClient.tileIcon) } flowOf(state) } @@ -135,28 +124,28 @@ constructor( explanation = context.getString( R.string.wallet_quick_affordance_unavailable_install_the_app - ), + ) ) queryCards().isEmpty() -> KeyguardQuickAffordanceConfig.PickerScreenState.Disabled( explanation = context.getString( R.string.wallet_quick_affordance_unavailable_configure_the_app - ), + ) ) else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default() } } override fun onTriggered( - expandable: Expandable?, + expandable: Expandable? ): KeyguardQuickAffordanceConfig.OnTriggeredResult { walletController.startQuickAccessUiIntent( activityStarter, expandable?.activityTransitionController(), /* hasCard= */ true, ) - return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(true) } private suspend fun queryCards(): List<WalletCard> { @@ -199,10 +188,8 @@ constructor( Icon.Loaded( drawable = tileIcon, contentDescription = - ContentDescription.Resource( - res = R.string.accessibility_wallet_button, - ), - ), + ContentDescription.Resource(res = R.string.accessibility_wallet_button), + ) ) } else { KeyguardQuickAffordanceConfig.LockScreenState.Hidden diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt index ae55825c9842..9c2daf52c5df 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt @@ -29,7 +29,6 @@ import com.android.keyguard.logging.KeyguardQuickAffordancesLogger import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.animation.Expandable import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled import com.android.systemui.dock.DockManager @@ -62,6 +61,7 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine @@ -101,6 +101,14 @@ constructor( val launchingAffordance: StateFlow<Boolean> = repository.get().launchingAffordance.asStateFlow() /** + * Whether a [KeyguardQuickAffordanceConfig.OnTriggeredResult] indicated that the system + * launched an activity or showed a dialog. + */ + private val _launchingFromTriggeredResult = + MutableStateFlow<KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult?>(null) + val launchingFromTriggeredResult = _launchingFromTriggeredResult.asStateFlow() + + /** * Whether the UI should use the long press gesture to activate quick affordances. * * If `false`, the UI goes back to using single taps. @@ -187,18 +195,45 @@ constructor( metricsLogger.logOnShortcutTriggered(slotId, configKey) when (val result = config.onTriggered(expandable)) { - is KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity -> + is KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity -> { + setLaunchingFromTriggeredResult( + KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult( + launched = true, + configKey, + ) + ) launchQuickAffordance( intent = result.intent, canShowWhileLocked = result.canShowWhileLocked, expandable = expandable, ) - is KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled -> Unit - is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog -> + } + is KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled -> { + setLaunchingFromTriggeredResult( + KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult( + result.actionLaunched, + configKey, + ) + ) + } + is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog -> { + setLaunchingFromTriggeredResult( + KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult( + launched = true, + configKey, + ) + ) showDialog(result.dialog, result.expandable) + } } } + fun setLaunchingFromTriggeredResult( + launchingResult: KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult? + ) { + _launchingFromTriggeredResult.value = launchingResult + } + /** * Selects an affordance with the given ID on the slot with the given ID. * diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt index aa44b6d46289..382436cf9397 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.domain.interactor.scenetransition +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.systemui.CoreStartable @@ -38,7 +39,6 @@ import java.util.UUID import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import com.android.app.tracing.coroutines.launchTraced as launch /** * This class listens to scene framework scene transitions and manages keyguard transition framework @@ -111,7 +111,10 @@ constructor( if (currentTransitionId == null) return if (prevTransition !is ObservableTransitionState.Transition) return - if (idle.currentScene == prevTransition.toContent) { + if ( + idle.currentScene == prevTransition.toContent || + idle.currentOverlays.contains(prevTransition.toContent) + ) { finishCurrentTransition() } else { val targetState = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt index e7803c5e964c..a4a5ba691965 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt @@ -17,12 +17,23 @@ package com.android.systemui.keyguard.ui.binder import android.os.VibrationEffect +import com.android.systemui.Flags import kotlin.time.Duration.Companion.milliseconds object KeyguardBottomAreaVibrations { - val ShakeAnimationDuration = 300.milliseconds - const val ShakeAnimationCycles = 5f + val ShakeAnimationDuration = + if (Flags.msdlFeedback()) { + 285.milliseconds + } else { + 300.milliseconds + } + val ShakeAnimationCycles = + if (Flags.msdlFeedback()) { + 3f + } else { + 5f + } private const val SmallVibrationScale = 0.3f private const val BigVibrationScale = 0.6f @@ -32,7 +43,7 @@ object KeyguardBottomAreaVibrations { .apply { val vibrationDelayMs = (ShakeAnimationDuration.inWholeMilliseconds / (ShakeAnimationCycles * 2)) - .toInt() + .toInt() val vibrationCount = ShakeAnimationCycles.toInt() * 2 repeat(vibrationCount) { @@ -47,29 +58,13 @@ object KeyguardBottomAreaVibrations { val Activated = VibrationEffect.startComposition() - .addPrimitive( - VibrationEffect.Composition.PRIMITIVE_TICK, - BigVibrationScale, - 0, - ) - .addPrimitive( - VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, - 0.1f, - 0, - ) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, BigVibrationScale, 0) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 0.1f, 0) .compose() val Deactivated = VibrationEffect.startComposition() - .addPrimitive( - VibrationEffect.Composition.PRIMITIVE_TICK, - BigVibrationScale, - 0, - ) - .addPrimitive( - VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, - 0.1f, - 0, - ) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, BigVibrationScale, 0) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.1f, 0) .compose() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt index 8725cdd273df..8a2e3dd791c2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt @@ -20,6 +20,7 @@ package com.android.systemui.keyguard.ui.binder import android.annotation.SuppressLint import android.content.res.ColorStateList import android.graphics.drawable.Animatable2 +import android.os.VibrationEffect import android.util.Size import android.view.View import android.view.ViewGroup @@ -33,25 +34,27 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.tracing.coroutines.launchTraced as launch import com.android.keyguard.logging.KeyguardQuickAffordancesLogger +import com.android.systemui.Flags import com.android.systemui.animation.Expandable import com.android.systemui.animation.view.LaunchableImageView import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceHapticViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.util.doOnEnd +import com.google.android.msdl.data.model.MSDLToken +import com.google.android.msdl.domain.MSDLPlayer import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map -import com.android.app.tracing.coroutines.launchTraced as launch /** This is only for a SINGLE Quick affordance */ @SysUISingleton @@ -60,8 +63,9 @@ class KeyguardQuickAffordanceViewBinder constructor( private val falsingManager: FalsingManager?, private val vibratorHelper: VibratorHelper?, + private val msdlPlayer: MSDLPlayer, private val logger: KeyguardQuickAffordancesLogger, - @Main private val mainImmediateDispatcher: CoroutineDispatcher, + private val hapticsViewModelFactory: KeyguardQuickAffordanceHapticViewModel.Factory, ) { private val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L @@ -88,6 +92,12 @@ constructor( ): Binding { val button = view as ImageView val configurationBasedDimensions = MutableStateFlow(loadFromResources(view)) + val hapticsViewModel = + if (Flags.msdlFeedback()) { + hapticsViewModelFactory.create(viewModel) + } else { + null + } val disposableHandle = view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { @@ -98,15 +108,12 @@ constructor( viewModel = buttonModel, messageDisplayer = messageDisplayer, ) + hapticsViewModel?.updateActivatedHistory(buttonModel.isActivated) } } launch { - updateButtonAlpha( - view = button, - viewModel = viewModel, - alphaFlow = alpha, - ) + updateButtonAlpha(view = button, viewModel = viewModel, alphaFlow = alpha) } launch { @@ -117,6 +124,32 @@ constructor( } } } + + if (Flags.msdlFeedback()) { + launch { + hapticsViewModel + ?.quickAffordanceHapticState + ?.filter { + it != + KeyguardQuickAffordanceHapticViewModel.HapticState + .NO_HAPTICS + } + ?.collect { state -> + when (state) { + KeyguardQuickAffordanceHapticViewModel.HapticState + .TOGGLE_ON -> msdlPlayer.playToken(MSDLToken.SWITCH_ON) + KeyguardQuickAffordanceHapticViewModel.HapticState + .TOGGLE_OFF -> + msdlPlayer.playToken(MSDLToken.SWITCH_OFF) + KeyguardQuickAffordanceHapticViewModel.HapticState.LAUNCH -> + msdlPlayer.playToken(MSDLToken.LONG_PRESS) + KeyguardQuickAffordanceHapticViewModel.HapticState + .NO_HAPTICS -> Unit + } + hapticsViewModel.resetLaunchingFromTriggeredResult() + } + } + } } } @@ -178,7 +211,7 @@ constructor( com.android.internal.R.color.materialColorOnPrimaryFixed } else { com.android.internal.R.color.materialColorOnSurface - }, + } ) ) @@ -221,12 +254,7 @@ constructor( .getDimensionPixelSize(R.dimen.keyguard_affordance_shake_amplitude) .toFloat() val shakeAnimator = - ObjectAnimator.ofFloat( - view, - "translationX", - -amplitude / 2, - amplitude / 2, - ) + ObjectAnimator.ofFloat(view, "translationX", -amplitude / 2, amplitude / 2) shakeAnimator.duration = KeyguardBottomAreaVibrations.ShakeAnimationDuration.inWholeMilliseconds shakeAnimator.interpolator = @@ -234,11 +262,17 @@ constructor( shakeAnimator.doOnEnd { view.translationX = 0f } shakeAnimator.start() - vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake) + vibratorHelper?.playFeedback(KeyguardBottomAreaVibrations.Shake, msdlPlayer) logger.logQuickAffordanceTapped(viewModel.configKey) } view.onLongClickListener = - OnLongClickListener(falsingManager, viewModel, vibratorHelper, onTouchListener) + OnLongClickListener( + falsingManager, + viewModel, + vibratorHelper, + onTouchListener, + msdlPlayer, + ) } else { view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager))) } @@ -268,7 +302,7 @@ constructor( Size( view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width), view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height), - ), + ) ) } @@ -297,7 +331,8 @@ constructor( private val falsingManager: FalsingManager?, private val viewModel: KeyguardQuickAffordanceViewModel, private val vibratorHelper: VibratorHelper?, - private val onTouchListener: KeyguardQuickAffordanceOnTouchListener + private val onTouchListener: KeyguardQuickAffordanceOnTouchListener, + private val msdlPlayer: MSDLPlayer, ) : View.OnLongClickListener { override fun onLongClick(view: View): Boolean { if (falsingManager?.isFalseLongTap(FalsingManager.MODERATE_PENALTY) == true) { @@ -312,12 +347,13 @@ constructor( slotId = viewModel.slotId, ) ) - vibratorHelper?.vibrate( + vibratorHelper?.playFeedback( if (viewModel.isActivated) { KeyguardBottomAreaVibrations.Activated } else { KeyguardBottomAreaVibrations.Deactivated - } + }, + msdlPlayer, ) } @@ -328,7 +364,15 @@ constructor( override fun onLongClickUseDefaultHapticFeedback(view: View) = false } - private data class ConfigurationBasedDimensions( - val buttonSizePx: Size, - ) + private data class ConfigurationBasedDimensions(val buttonSizePx: Size) +} + +private fun VibratorHelper.playFeedback(effect: VibrationEffect, msdlPlayer: MSDLPlayer) { + if (!Flags.msdlFeedback()) { + vibrate(effect) + } else { + if (effect == KeyguardBottomAreaVibrations.Shake) { + msdlPlayer.playToken(MSDLToken.FAILURE) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index a2ce4ec5ce9b..6d270b219c81 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -127,21 +127,18 @@ object KeyguardRootViewBinder { if (Flags.nonTouchscreenDevicesBypassFalsing()) { if ( event.action == MotionEvent.ACTION_DOWN && - event.buttonState == MotionEvent.BUTTON_PRIMARY && - !event.isTouchscreenSource() + event.buttonState == MotionEvent.BUTTON_PRIMARY && + !event.isTouchscreenSource() ) { consumed = true } else if ( - event.action == MotionEvent.ACTION_UP && - !event.isTouchscreenSource() + event.action == MotionEvent.ACTION_UP && !event.isTouchscreenSource() ) { statusBarKeyguardViewManager?.showBouncer(true) consumed = true } } - viewModel.setRootViewLastTapPosition( - Point(event.x.toInt(), event.y.toInt()) - ) + viewModel.setRootViewLastTapPosition(Point(event.x.toInt(), event.y.toInt())) } consumed } @@ -172,7 +169,6 @@ object KeyguardRootViewBinder { launch("$TAG#alpha") { viewModel.alpha(viewState).collect { alpha -> view.alpha = alpha - childViews[statusViewId]?.alpha = alpha childViews[burnInLayerId]?.alpha = alpha } } @@ -253,18 +249,6 @@ object KeyguardRootViewBinder { } launch { - viewModel.burnInLayerAlpha.collect { alpha -> - childViews[statusViewId]?.alpha = alpha - } - } - - launch { - viewModel.lockscreenStateAlpha(viewState).collect { alpha -> - childViews[statusViewId]?.alpha = alpha - } - } - - launch { viewModel.scale.collect { scaleViewModel -> if (scaleViewModel.scaleClockOnly) { // For clocks except weather clock, we have scale transition besides @@ -553,7 +537,6 @@ object KeyguardRootViewBinder { return device?.supportsSource(InputDevice.SOURCE_TOUCHSCREEN) == true } - private val statusViewId = R.id.keyguard_status_view private val burnInLayerId = R.id.burn_in_layer private val aodNotificationIconContainerId = R.id.aod_notification_icon_container private val largeClockId = customR.id.lockscreen_clock_view_large diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index 090b65922d2d..6fb31c0e4191 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -48,19 +48,16 @@ import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import androidx.core.view.isInvisible -import com.android.internal.policy.SystemBarUtils import com.android.keyguard.ClockEventController -import com.android.keyguard.KeyguardClockSwitch import com.android.systemui.animation.view.LaunchableImageView import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.communal.ui.binder.CommunalTutorialIndicatorViewBinder import com.android.systemui.communal.ui.viewmodel.CommunalTutorialIndicatorViewModel -import com.android.systemui.coroutines.newTracingContext +import com.android.systemui.customization.R as customR import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.shared.model.ClockSizeSetting import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder @@ -80,7 +77,6 @@ import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shared.clocks.ClockRegistry -import com.android.systemui.shared.clocks.DefaultClockController import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants @@ -91,18 +87,13 @@ import com.android.systemui.util.settings.SecureSettings import dagger.assisted.Assisted import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.Job -import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import org.json.JSONException import org.json.JSONObject -import com.android.app.tracing.coroutines.launchTraced as launch -import com.android.systemui.customization.R as customR /** Renders the preview of the lock screen. */ class KeyguardPreviewRenderer @@ -110,7 +101,6 @@ class KeyguardPreviewRenderer @AssistedInject constructor( @Application private val context: Context, - @Application applicationScope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, @Main private val mainHandler: Handler, @Background private val backgroundDispatcher: CoroutineDispatcher, @@ -157,8 +147,6 @@ constructor( val surfacePackage: SurfaceControlViewHost.SurfacePackage get() = checkNotNull(host.surfacePackage) - private lateinit var largeClockHostView: FrameLayout - private lateinit var smallClockHostView: FrameLayout private var smartSpaceView: View? = null private val disposables = DisposableHandles() @@ -166,29 +154,18 @@ constructor( private val shortcutsBindings = mutableSetOf<KeyguardQuickAffordanceViewBinder.Binding>() - private val coroutineScope: CoroutineScope - @Style.Type private var themeStyle: Int? = null init { - coroutineScope = - CoroutineScope( - applicationScope.coroutineContext + - Job() + - newTracingContext("KeyguardPreviewRenderer") - ) - disposables += DisposableHandle { coroutineScope.cancel() } clockController.setFallbackWeatherData(WeatherData.getPlaceholderWeatherData()) - quickAffordancesCombinedViewModel.enablePreviewMode( initiallySelectedSlotId = - bundle.getString(KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID) - ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, + bundle.getString(KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID) + ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance, ) - if (MigrateClocksToBlueprint.isEnabled) { - clockViewModel.shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance - } + + clockViewModel.shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance runBlocking(mainDispatcher) { host = SurfaceControlViewHost( @@ -348,6 +325,7 @@ constructor( smartSpaceView?.alpha = if (shouldHighlightSelectedAffordance) DIM_ALPHA else 1.0f } + @OptIn(ExperimentalCoroutinesApi::class) private fun setupKeyguardRootView(previewContext: Context, rootView: FrameLayout) { val keyguardRootView = KeyguardRootView(previewContext, null) rootView.addView( @@ -358,34 +336,23 @@ constructor( ), ) - setUpUdfps( - previewContext, - if (MigrateClocksToBlueprint.isEnabled) keyguardRootView else rootView, - ) + setUpUdfps(previewContext, keyguardRootView) setupShortcuts(keyguardRootView) if (!shouldHideClock) { setUpClock(previewContext, rootView) - if (MigrateClocksToBlueprint.isEnabled) { - KeyguardPreviewClockViewBinder.bind( - keyguardRootView, - clockViewModel, - clockRegistry, - ::updateClockAppearance, - ClockPreviewConfig( - previewContext, - getPreviewShadeLayoutWide(display!!), - SceneContainerFlag.isEnabled, - ), - ) - } else { - KeyguardPreviewClockViewBinder.bind( - largeClockHostView, - smallClockHostView, - clockViewModel, - ) - } + KeyguardPreviewClockViewBinder.bind( + keyguardRootView, + clockViewModel, + clockRegistry, + ::updateClockAppearance, + ClockPreviewConfig( + previewContext, + getPreviewShadeLayoutWide(display!!), + SceneContainerFlag.isEnabled, + ), + ) } setUpSmartspace(previewContext, rootView) @@ -451,82 +418,22 @@ constructor( .inflate(R.layout.udfps_keyguard_preview, parentView, false) as View // Place the UDFPS view in the proper sensor location - if (MigrateClocksToBlueprint.isEnabled) { - val lockId = KeyguardPreviewClockViewBinder.lockId - finger.id = lockId - parentView.addView(finger) - val cs = ConstraintSet() - cs.clone(parentView as ConstraintLayout) - cs.apply { - constrainWidth(lockId, sensorBounds.width()) - constrainHeight(lockId, sensorBounds.height()) - connect(lockId, TOP, PARENT_ID, TOP, sensorBounds.top) - connect(lockId, START, PARENT_ID, START, sensorBounds.left) - } - cs.applyTo(parentView) - } else { - val fingerprintLayoutParams = - FrameLayout.LayoutParams(sensorBounds.width(), sensorBounds.height()) - fingerprintLayoutParams.setMarginsRelative( - sensorBounds.left, - sensorBounds.top, - sensorBounds.right, - sensorBounds.bottom, - ) - parentView.addView(finger, fingerprintLayoutParams) + val lockId = KeyguardPreviewClockViewBinder.lockId + finger.id = lockId + parentView.addView(finger) + val cs = ConstraintSet() + cs.clone(parentView as ConstraintLayout) + cs.apply { + constrainWidth(lockId, sensorBounds.width()) + constrainHeight(lockId, sensorBounds.height()) + connect(lockId, TOP, PARENT_ID, TOP, sensorBounds.top) + connect(lockId, START, PARENT_ID, START, sensorBounds.left) } + cs.applyTo(parentView) } private fun setUpClock(previewContext: Context, parentView: ViewGroup) { val resources = parentView.resources - if (!MigrateClocksToBlueprint.isEnabled) { - largeClockHostView = FrameLayout(previewContext) - largeClockHostView.layoutParams = - FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT, - ) - largeClockHostView.isInvisible = true - parentView.addView(largeClockHostView) - - smallClockHostView = FrameLayout(previewContext) - val layoutParams = - FrameLayout.LayoutParams( - FrameLayout.LayoutParams.WRAP_CONTENT, - resources.getDimensionPixelSize(customR.dimen.small_clock_height), - ) - layoutParams.topMargin = - SystemBarUtils.getStatusBarHeight(previewContext) + - resources.getDimensionPixelSize(customR.dimen.small_clock_padding_top) - smallClockHostView.layoutParams = layoutParams - smallClockHostView.setPaddingRelative( - /* start = */ resources.getDimensionPixelSize(customR.dimen.clock_padding_start), - /* top = */ 0, - /* end = */ 0, - /* bottom = */ 0, - ) - smallClockHostView.clipChildren = false - parentView.addView(smallClockHostView) - smallClockHostView.isInvisible = true - } - - // TODO (b/283465254): Move the listeners to KeyguardClockRepository - if (!MigrateClocksToBlueprint.isEnabled) { - val clockChangeListener = - object : ClockRegistry.ClockChangeListener { - override fun onCurrentClockChanged() { - onClockChanged() - } - } - clockRegistry.registerClockChangeListener(clockChangeListener) - disposables += DisposableHandle { - clockRegistry.unregisterClockChangeListener(clockChangeListener) - } - - clockController.registerListeners(parentView) - disposables += DisposableHandle { clockController.unregisterListeners() } - } - val receiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { @@ -544,38 +451,9 @@ constructor( }, ) disposables += DisposableHandle { broadcastDispatcher.unregisterReceiver(receiver) } - - if (!MigrateClocksToBlueprint.isEnabled) { - val layoutChangeListener = - View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> - if (clockController.clock !is DefaultClockController) { - clockController.clock - ?.largeClock - ?.events - ?.onTargetRegionChanged( - KeyguardClockSwitch.getLargeClockRegion(parentView) - ) - clockController.clock - ?.smallClock - ?.events - ?.onTargetRegionChanged( - KeyguardClockSwitch.getSmallClockRegion(parentView) - ) - } - } - parentView.addOnLayoutChangeListener(layoutChangeListener) - disposables += DisposableHandle { - parentView.removeOnLayoutChangeListener(layoutChangeListener) - } - } - - onClockChanged() } private suspend fun updateClockAppearance(clock: ClockController, resources: Resources) { - if (!MigrateClocksToBlueprint.isEnabled) { - clockController.clock = clock - } val colors = wallpaperColors if (clockRegistry.seedColor == null && colors != null) { // Seed color null means users do not override any color on the clock. The default @@ -601,9 +479,7 @@ constructor( // In clock preview, we should have a seed color for clock // before setting clock to clockEventController to avoid updateColor with seedColor == null // So in update colors, it should already have the correct theme in clockFaceController - if (MigrateClocksToBlueprint.isEnabled) { - clockController.clock = clock - } + clockController.clock = clock // When set clock to clockController,it will reset fontsize based on context.resources // We need to override it with overlaid resources clock.largeClock.events.onFontSettingChanged( @@ -611,19 +487,6 @@ constructor( ) } - private fun onClockChanged() { - if (MigrateClocksToBlueprint.isEnabled) { - return - } - coroutineScope.launch { - val clock = clockRegistry.createCurrentClock() - clockController.clock = clock - updateClockAppearance(clock, context.resources) - updateLargeClock(clock) - updateSmallClock(clock) - } - } - private fun setupCommunalTutorialIndicator(keyguardRootView: ConstraintLayout) { keyguardRootView.findViewById<TextView>(R.id.communal_tutorial_indicator)?.let { indicatorView -> @@ -657,34 +520,6 @@ constructor( } } - private fun updateLargeClock(clock: ClockController) { - if (MigrateClocksToBlueprint.isEnabled) { - return - } - clock.largeClock.events.onTargetRegionChanged( - KeyguardClockSwitch.getLargeClockRegion(largeClockHostView) - ) - if (shouldHighlightSelectedAffordance) { - clock.largeClock.view.alpha = DIM_ALPHA - } - largeClockHostView.removeAllViews() - largeClockHostView.addView(clock.largeClock.view) - } - - private fun updateSmallClock(clock: ClockController) { - if (MigrateClocksToBlueprint.isEnabled) { - return - } - clock.smallClock.events.onTargetRegionChanged( - KeyguardClockSwitch.getSmallClockRegion(smallClockHostView) - ) - if (shouldHighlightSelectedAffordance) { - clock.smallClock.view.alpha = DIM_ALPHA - } - smallClockHostView.removeAllViews() - smallClockHostView.addView(clock.smallClock.view) - } - private fun getPreviewShadeLayoutWide(display: Display): Boolean { return if (display.displayId == 0) { shadeInteractor.isShadeLayoutWide.value diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt index 160380bb09bc..57fe15d4f52c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt @@ -24,7 +24,6 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel @@ -50,9 +49,6 @@ constructor( } override fun addViews(constraintLayout: ConstraintLayout) { - if (!MigrateClocksToBlueprint.isEnabled) { - return - } if (emptyView.parent != null) { // As emptyView is lazy, it might be already attached. (emptyView.parent as? ViewGroup)?.removeView(emptyView) @@ -68,17 +64,10 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - if (!MigrateClocksToBlueprint.isEnabled) { - return - } clockViewModel.burnInLayer = burnInLayer } override fun applyConstraints(constraintSet: ConstraintSet) { - if (!MigrateClocksToBlueprint.isEnabled) { - return - } - constraintSet.apply { // The empty view should not occupy any space constrainHeight(R.id.burn_in_layer_empty_view, 1) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt index 70bf8bca55b9..738fb73a4918 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt @@ -32,7 +32,6 @@ import androidx.constraintlayout.widget.ConstraintSet.VISIBLE import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT import com.android.systemui.customization.R as customR import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.shared.model.KeyguardSection @@ -82,9 +81,6 @@ constructor( override fun addViews(constraintLayout: ConstraintLayout) {} override fun bindData(constraintLayout: ConstraintLayout) { - if (!MigrateClocksToBlueprint.isEnabled) { - return - } disposableHandle?.dispose() disposableHandle = KeyguardClockViewBinder.bind( @@ -99,20 +95,12 @@ constructor( } override fun applyConstraints(constraintSet: ConstraintSet) { - if (!MigrateClocksToBlueprint.isEnabled) { - return - } - keyguardClockViewModel.currentClock.value?.let { clock -> constraintSet.applyDeltaFrom(buildConstraints(clock, constraintSet)) } } override fun removeViews(constraintLayout: ConstraintLayout) { - if (!MigrateClocksToBlueprint.isEnabled) { - return - } - disposableHandle?.dispose() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt index 3a791fd45528..4bfe5f0458c5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt @@ -24,7 +24,6 @@ import androidx.constraintlayout.widget.ConstraintSet.END import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.res.R import com.android.systemui.shade.LargeScreenHeaderHelper import com.android.systemui.shade.NotificationPanelView @@ -54,32 +53,25 @@ constructor( sharedNotificationContainerBinder, ) { override fun applyConstraints(constraintSet: ConstraintSet) { - if (!MigrateClocksToBlueprint.isEnabled) { - return - } constraintSet.apply { val bottomMargin = context.resources.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin) - if (MigrateClocksToBlueprint.isEnabled) { - val useLargeScreenHeader = - context.resources.getBoolean(R.bool.config_use_large_screen_shade_header) - val marginTopLargeScreen = - largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight() - connect( - R.id.nssl_placeholder, - TOP, - R.id.smart_space_barrier_bottom, - BOTTOM, - bottomMargin + - if (useLargeScreenHeader) { - marginTopLargeScreen - } else { - 0 - } - ) - } else { - connect(R.id.nssl_placeholder, TOP, R.id.keyguard_status_view, BOTTOM, bottomMargin) - } + val useLargeScreenHeader = + context.resources.getBoolean(R.bool.config_use_large_screen_shade_header) + val marginTopLargeScreen = + largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight() + connect( + R.id.nssl_placeholder, + TOP, + R.id.smart_space_barrier_bottom, + BOTTOM, + bottomMargin + + if (useLargeScreenHeader) { + marginTopLargeScreen + } else { + 0 + }, + ) connect(R.id.nssl_placeholder, START, PARENT_ID, START) connect(R.id.nssl_placeholder, END, PARENT_ID, END) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt index 620cc13a0c3a..fc26d18fde6b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt @@ -25,7 +25,6 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.TOP -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView @@ -62,9 +61,6 @@ constructor( } override fun addViews(constraintLayout: ConstraintLayout) { - if (!MigrateClocksToBlueprint.isEnabled) { - return - } // This moves the existing NSSL view to a different parent, as the controller is a // singleton and recreating it has other bad side effects. // In the SceneContainer, this is done by the NotificationSection composable. @@ -78,10 +74,6 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - if (!MigrateClocksToBlueprint.isEnabled) { - return - } - disposableHandle?.dispose() disposableHandle = sharedNotificationContainerBinder.bind( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt index 73e14b1524f3..cd038d799f42 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt @@ -26,7 +26,6 @@ import androidx.constraintlayout.widget.ConstraintSet import com.android.systemui.customization.R as customR import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.KeyguardUnlockAnimationController -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor import com.android.systemui.keyguard.shared.model.KeyguardSection @@ -70,7 +69,6 @@ constructor( } override fun addViews(constraintLayout: ConstraintLayout) { - if (!MigrateClocksToBlueprint.isEnabled) return if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return smartspaceView = smartspaceController.buildAndConnectView(constraintLayout) weatherView = smartspaceController.buildAndConnectWeatherView(constraintLayout) @@ -98,7 +96,6 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - if (!MigrateClocksToBlueprint.isEnabled) return if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return disposableHandle?.dispose() disposableHandle = @@ -111,13 +108,11 @@ constructor( } override fun applyConstraints(constraintSet: ConstraintSet) { - if (!MigrateClocksToBlueprint.isEnabled) return if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return val dateWeatherPaddingStart = KeyguardSmartspaceViewModel.getDateWeatherStartMargin(context) val smartspaceHorizontalPadding = KeyguardSmartspaceViewModel.getSmartspaceHorizontalMargin(context) constraintSet.apply { - // migrate addDateWeatherView, addWeatherView from KeyguardClockSwitchController constrainHeight(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT) constrainWidth(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT) connect( @@ -128,7 +123,6 @@ constructor( dateWeatherPaddingStart, ) - // migrate addSmartspaceView from KeyguardClockSwitchController constrainHeight(sharedR.id.bc_smartspace_view, ConstraintSet.WRAP_CONTENT) constrainWidth(sharedR.id.bc_smartspace_view, ConstraintSet.MATCH_CONSTRAINT) connect( @@ -182,7 +176,6 @@ constructor( } override fun removeViews(constraintLayout: ConstraintLayout) { - if (!MigrateClocksToBlueprint.isEnabled) return if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return listOf(smartspaceView, dateWeatherView).forEach { it?.let { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt index 729759a9ad00..5d463f72d8b2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt @@ -23,7 +23,6 @@ import androidx.constraintlayout.widget.ConstraintSet.END import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView import com.android.systemui.shade.ShadeDisplayAware @@ -50,16 +49,13 @@ constructor( sharedNotificationContainerBinder, ) { override fun applyConstraints(constraintSet: ConstraintSet) { - if (!MigrateClocksToBlueprint.isEnabled) { - return - } constraintSet.apply { connect( R.id.nssl_placeholder, TOP, PARENT_ID, TOP, - context.resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin) + context.resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin), ) connect(R.id.nssl_placeholder, START, PARENT_ID, START) connect(R.id.nssl_placeholder, END, PARENT_ID, END) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt index 1c897237fe89..fb311a533aa2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt @@ -24,7 +24,6 @@ import com.android.app.animation.Interpolators import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor @@ -194,12 +193,7 @@ constructor( (!useAltAod) && keyguardClockViewModel.clockSize.value == ClockSize.LARGE val burnInY = MathUtils.lerp(0, burnIn.translationY, interpolated).toInt() - val translationY = - if (MigrateClocksToBlueprint.isEnabled) { - max(params.topInset - params.minViewY, burnInY) - } else { - max(params.topInset, params.minViewY + burnInY) - params.minViewY - } + val translationY = max(params.topInset - params.minViewY, burnInY) BurnInModel( translationX = MathUtils.lerp(0, burnIn.translationX, interpolated).toInt(), translationY = translationY, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceHapticViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceHapticViewModel.kt new file mode 100644 index 000000000000..890628c31c55 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceHapticViewModel.kt @@ -0,0 +1,96 @@ +/* + * 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.keyguard.ui.viewmodel + +import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge + +class KeyguardQuickAffordanceHapticViewModel +@AssistedInject +constructor( + @Assisted quickAffordanceViewModel: Flow<KeyguardQuickAffordanceViewModel>, + private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor, +) { + + private val activatedHistory = MutableStateFlow(ActivatedHistory(false)) + + private val launchingHapticState: Flow<HapticState> = + combine( + quickAffordanceViewModel.map { it.configKey }, + quickAffordanceInteractor.launchingFromTriggeredResult, + ) { key, launchingResult -> + val validKey = key != null && key == launchingResult?.configKey + if (validKey && launchingResult?.launched == true) { + HapticState.LAUNCH + } else { + HapticState.NO_HAPTICS + } + } + .distinctUntilChanged() + + private val toggleHapticState: Flow<HapticState> = + activatedHistory + .map { history -> + when { + history.previousValue == false && history.currentValue -> HapticState.TOGGLE_ON + history.previousValue == true && !history.currentValue -> HapticState.TOGGLE_OFF + else -> HapticState.NO_HAPTICS + } + } + .distinctUntilChanged() + + val quickAffordanceHapticState = + merge(launchingHapticState, toggleHapticState).distinctUntilChanged() + + fun resetLaunchingFromTriggeredResult() = + quickAffordanceInteractor.setLaunchingFromTriggeredResult(null) + + fun updateActivatedHistory(isActivated: Boolean) { + activatedHistory.value = + ActivatedHistory( + currentValue = isActivated, + previousValue = activatedHistory.value.currentValue, + ) + } + + enum class HapticState { + TOGGLE_ON, + TOGGLE_OFF, + LAUNCH, + NO_HAPTICS, + } + + private data class ActivatedHistory( + val currentValue: Boolean, + val previousValue: Boolean? = null, + ) + + @AssistedFactory + interface Factory { + fun create( + quickAffordanceViewModel: Flow<KeyguardQuickAffordanceViewModel> + ): KeyguardQuickAffordanceHapticViewModel + } +} 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 9066d466ceca..eaba5d5a149c 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 @@ -29,7 +29,6 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.PulseExpansionInteractor import com.android.systemui.keyguard.shared.model.Edge -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.GONE @@ -130,7 +129,6 @@ constructor( PrimaryBouncerToLockscreenTransitionViewModel, private val screenOffAnimationController: ScreenOffAnimationController, private val aodBurnInViewModel: AodBurnInViewModel, - private val aodAlphaViewModel: AodAlphaViewModel, private val shadeInteractor: ShadeInteractor, ) { val burnInLayerVisibility: Flow<Int> = @@ -284,15 +282,6 @@ constructor( .distinctUntilChanged() } - /** Specific alpha value for elements visible during [KeyguardState.LOCKSCREEN] */ - @Deprecated("only used for legacy status view") - fun lockscreenStateAlpha(viewState: ViewStateAccessor): Flow<Float> { - return aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState) - } - - /** For elements that appear and move during the animation -> AOD */ - val burnInLayerAlpha: Flow<Float> = aodAlphaViewModel.alpha - val translationY: Flow<Float> = aodBurnInViewModel.movement.map { it.translationY.toFloat() } val translationX: Flow<StateToValue> = diff --git a/packages/SystemUI/src/com/android/systemui/lottie/LottieTaskExt.kt b/packages/SystemUI/src/com/android/systemui/lottie/LottieTaskExt.kt new file mode 100644 index 000000000000..dd2525f5ca45 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/lottie/LottieTaskExt.kt @@ -0,0 +1,49 @@ +/* + * 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.lottie + +import com.airbnb.lottie.LottieComposition +import com.airbnb.lottie.LottieListener +import com.airbnb.lottie.LottieTask +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlinx.coroutines.suspendCancellableCoroutine + +/** + * Suspends until [LottieTask] is finished with a result or a failure. + * + * @return result of the [LottieTask] when it's successful + */ +suspend fun LottieTask<LottieComposition>.await() = + suspendCancellableCoroutine<LottieComposition> { continuation -> + val resultListener = + LottieListener<LottieComposition> { result -> + with(continuation) { if (!isCancelled && !isCompleted) resume(result) } + } + val failureListener = + LottieListener<Throwable> { throwable -> + with(continuation) { + if (!isCancelled && !isCompleted) resumeWithException(throwable) + } + } + addListener(resultListener) + addFailureListener(failureListener) + continuation.invokeOnCancellation { + removeListener(resultListener) + removeFailureListener(failureListener) + } + } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt index c32bd403d2e8..b4dabbe036e9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt @@ -34,13 +34,14 @@ import android.view.ViewGroup import android.view.ViewGroupOverlay import androidx.annotation.VisibleForTesting import com.android.app.animation.Interpolators +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.app.tracing.traceSection import com.android.keyguard.KeyguardViewController import com.android.systemui.Flags.mediaControlsLockscreenShadeBugFix import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dreams.DreamOverlayStateController import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -68,7 +69,6 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.mapLatest -import com.android.app.tracing.coroutines.launchTraced as launch private val TAG: String = MediaHierarchyManager::class.java.simpleName @@ -115,7 +115,7 @@ constructor( wakefulnessLifecycle: WakefulnessLifecycle, shadeInteractor: ShadeInteractor, private val secureSettings: SecureSettings, - @Main private val handler: Handler, + @Background private val handler: Handler, @Application private val coroutineScope: CoroutineScope, private val splitShadeStateController: SplitShadeStateController, private val logger: MediaViewLogger, @@ -631,7 +631,7 @@ constructor( } } } - secureSettings.registerContentObserverForUserSync( + secureSettings.registerContentObserverForUserAsync( Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, settingsObserver, UserHandle.USER_ALL, diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java index 574ccee28faa..ab998d10287f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java @@ -366,7 +366,6 @@ public abstract class MediaOutputBaseAdapter extends / (double) seekBar.getMax()); mVolumeValueText.setText(mContext.getResources().getString( R.string.media_output_dialog_volume_percentage, percentage)); - mVolumeValueText.setVisibility(View.VISIBLE); if (mStartFromMute) { updateUnmutedVolumeIcon(device); mStartFromMute = false; diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt index 311cbfb7e632..b2696aeaabfc 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt @@ -132,7 +132,7 @@ constructor( val isDefaultNotesAppSet = noteTaskInfoResolver.resolveInfo( QUICK_AFFORDANCE, - user = controller.getUserForHandlingNotesTaking(QUICK_AFFORDANCE) + user = controller.getUserForHandlingNotesTaking(QUICK_AFFORDANCE), ) != null return when { isEnabled && isDefaultNotesAppSet -> PickerScreenState.Default() @@ -158,7 +158,7 @@ constructor( override fun onTriggered(expandable: Expandable?): OnTriggeredResult { controller.showNoteTask(entryPoint = QUICK_AFFORDANCE) - return OnTriggeredResult.Handled + return OnTriggeredResult.Handled(true) } } @@ -194,7 +194,7 @@ private fun RoleManager.createNotesRoleFlow( fun isDefaultNotesAppSetForUser() = noteTaskInfoResolver.resolveInfo( QUICK_AFFORDANCE, - user = noteTaskController.getUserForHandlingNotesTaking(QUICK_AFFORDANCE) + user = noteTaskController.getUserForHandlingNotesTaking(QUICK_AFFORDANCE), ) != null trySendBlocking(isDefaultNotesAppSetForUser()) diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt index 91a3120ec770..1e608af14568 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt @@ -67,6 +67,7 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.shade.ShadeDisplayAware +import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor import com.android.systemui.shared.system.SysUiStatsLog import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.util.DeviceConfigProxy @@ -141,7 +142,6 @@ interface FgsManagerController { class FgsManagerControllerImpl @Inject constructor( - @ShadeDisplayAware private val context: Context, @ShadeDisplayAware private val resources: Resources, @Main private val mainExecutor: Executor, @Background private val backgroundExecutor: Executor, @@ -155,6 +155,7 @@ constructor( private val broadcastDispatcher: BroadcastDispatcher, private val dumpManager: DumpManager, private val systemUIDialogFactory: SystemUIDialog.Factory, + private val shadeDialogContextRepository: ShadeDialogContextInteractor, ) : Dumpable, FgsManagerController { companion object { @@ -388,7 +389,7 @@ constructor( override fun showDialog(expandable: Expandable?) { synchronized(lock) { if (dialog == null) { - val dialog = systemUIDialogFactory.create(context) + val dialog = systemUIDialogFactory.create(shadeDialogContextRepository.context) dialog.setTitle(R.string.fgs_manager_dialog_title) dialog.setMessage(R.string.fgs_manager_dialog_message) diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt index 790793eab258..3049a40f18c4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt @@ -17,16 +17,16 @@ package com.android.systemui.qs.composefragment.ui import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.ClipOp import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.CompositingStrategy import androidx.compose.ui.graphics.drawscope.clipRect -import androidx.compose.ui.graphics.layer.CompositingStrategy -import androidx.compose.ui.graphics.layer.drawLayer +import androidx.compose.ui.graphics.graphicsLayer /** * Clipping modifier for clipping out the notification scrim as it slides over QS. It will clip out @@ -34,16 +34,16 @@ import androidx.compose.ui.graphics.layer.drawLayer * from the QS container. */ fun Modifier.notificationScrimClip(clipParams: () -> NotificationScrimClipParams): Modifier { - return this.drawWithCache { + return this.graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen } + .drawWithContent { + drawContent() val params = clipParams() val left = -params.leftInset.toFloat() val right = size.width + params.rightInset.toFloat() val top = params.top.toFloat() val bottom = params.bottom.toFloat() - val graphicsLayer = obtainGraphicsLayer() - graphicsLayer.compositingStrategy = CompositingStrategy.Offscreen - graphicsLayer.record { - drawContent() + val clipSize = Size(right - left, bottom - top) + if (!clipSize.isEmpty()) { clipRect { drawRoundRect( color = Color.Black, @@ -54,9 +54,6 @@ fun Modifier.notificationScrimClip(clipParams: () -> NotificationScrimClipParams ) } } - onDrawWithContent { - drawLayer(graphicsLayer) - } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java index 873059ee08db..e7fa27159e9e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java @@ -675,17 +675,11 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta } private void add() { - if (addFromPosition(getLayoutPosition())) { - itemView.announceForAccessibility( - itemView.getContext().getText(R.string.accessibility_qs_edit_tile_added)); - } + addFromPosition(getLayoutPosition()); } private void remove() { - if (removeFromPosition(getLayoutPosition())) { - itemView.announceForAccessibility( - itemView.getContext().getText(R.string.accessibility_qs_edit_tile_removed)); - } + removeFromPosition(getLayoutPosition()); } boolean isCurrentTile() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt index 85db95203b45..f3c06a481fc2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt @@ -54,7 +54,7 @@ fun EditModeButton( ) { Icon( imageVector = Icons.Default.Edit, - contentDescription = stringResource(id = R.string.qs_edit), + contentDescription = stringResource(id = R.string.accessibility_quick_settings_edit), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java index 42a0cb1004f4..b7ff63cdc1fb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java @@ -41,6 +41,7 @@ import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.res.R; +import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.DataSaverController; @@ -56,6 +57,7 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements private final DataSaverController mDataSaverController; private final DialogTransitionAnimator mDialogTransitionAnimator; private final SystemUIDialog.Factory mSystemUIDialogFactory; + private final ShadeDialogContextInteractor mShadeDialogContextInteractor; @Inject public DataSaverTile( @@ -70,13 +72,15 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements QSLogger qsLogger, DataSaverController dataSaverController, DialogTransitionAnimator dialogTransitionAnimator, - SystemUIDialog.Factory systemUIDialogFactory + SystemUIDialog.Factory systemUIDialogFactory, + ShadeDialogContextInteractor shadeDialogContextInteractor ) { super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mDataSaverController = dataSaverController; mDialogTransitionAnimator = dialogTransitionAnimator; mSystemUIDialogFactory = systemUIDialogFactory; + mShadeDialogContextInteractor = shadeDialogContextInteractor; mDataSaverController.observe(getLifecycle(), this); } @@ -102,7 +106,8 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements // Show a dialog to confirm first. Dialogs shown by the DialogTransitionAnimator must be // created and shown on the main thread, so we post it to the UI handler. mUiHandler.post(() -> { - SystemUIDialog dialog = mSystemUIDialogFactory.create(mContext); + SystemUIDialog dialog = mSystemUIDialogFactory.create( + mShadeDialogContextInteractor.getContext()); dialog.setTitle(com.android.internal.R.string.data_saver_enable_title); dialog.setMessage(com.android.internal.R.string.data_saver_description); dialog.setPositiveButton(com.android.internal.R.string.data_saver_enable_button, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java index 19b45d50c594..7516ca030d4b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java @@ -193,7 +193,7 @@ public class InternetAdapter extends RecyclerView.Adapter<InternetAdapter.Intern if (mJob == null) { mJob = WifiUtils.checkWepAllowed(mContext, mCoroutineScope, wifiEntry.getSsid(), WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, intent -> { - mInternetDialogController.startActivity(intent, view); + mInternetDialogController.startActivityForDialog(intent); return null; }, () -> { wifiConnect(wifiEntry, view); @@ -211,7 +211,7 @@ public class InternetAdapter extends RecyclerView.Adapter<InternetAdapter.Intern true /* connectForCaller */); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - mContext.startActivity(intent); + mInternetDialogController.startActivityForDialog(intent); return; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index dbe1ae90b3f6..7036ef914a1c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -781,6 +781,10 @@ public class InternetDialogController implements AccessPointController.AccessPoi mActivityStarter.postStartActivityDismissingKeyguard(intent, 0, controller); } + void startActivityForDialog(Intent intent) { + mActivityStarter.startActivity(intent, false /* dismissShade */); + } + void launchNetworkSetting(View view) { startActivity(getSettingsIntent(), view); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java index 70c2a2a0d55a..5e9deec58c58 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java @@ -72,6 +72,7 @@ import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.res.R; import com.android.systemui.shade.ShadeDisplayAware; +import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.wifitrackerlib.WifiEntry; @@ -104,9 +105,9 @@ public class InternetDialogDelegate implements private final Handler mHandler; private final Executor mBackgroundExecutor; private final DialogTransitionAnimator mDialogTransitionAnimator; - private final Context mContext; private final boolean mAboveStatusBar; private final SystemUIDialog.Factory mSystemUIDialogFactory; + private final ShadeDialogContextInteractor mShadeDialogContextInteractor; @VisibleForTesting protected InternetAdapter mAdapter; @@ -204,10 +205,11 @@ public class InternetDialogDelegate implements @Main Handler handler, @Background Executor executor, KeyguardStateController keyguardStateController, - SystemUIDialog.Factory systemUIDialogFactory) { - mContext = context; + SystemUIDialog.Factory systemUIDialogFactory, + ShadeDialogContextInteractor shadeDialogContextInteractor) { mAboveStatusBar = aboveStatusBar; mSystemUIDialogFactory = systemUIDialogFactory; + mShadeDialogContextInteractor = shadeDialogContextInteractor; if (DEBUG) { Log.d(TAG, "Init InternetDialog"); } @@ -230,7 +232,8 @@ public class InternetDialogDelegate implements @Override public SystemUIDialog createDialog() { - SystemUIDialog dialog = mSystemUIDialogFactory.create(this, mContext); + SystemUIDialog dialog = mSystemUIDialogFactory.create(this, + mShadeDialogContextInteractor.getContext()); if (!mAboveStatusBar) { dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt index 8c54ab40c680..862dba1e7294 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt @@ -17,7 +17,6 @@ package com.android.systemui.qs.user import android.app.Dialog -import android.content.Context import android.content.DialogInterface import android.content.DialogInterface.BUTTON_NEUTRAL import android.content.Intent @@ -34,6 +33,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.QSUserSwitcherEvent import com.android.systemui.qs.tiles.UserDetailView import com.android.systemui.res.R +import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.user.ui.dialog.DialogShowerImpl import javax.inject.Inject @@ -50,6 +50,7 @@ constructor( private val dialogTransitionAnimator: DialogTransitionAnimator, private val uiEventLogger: UiEventLogger, private val dialogFactory: SystemUIDialog.Factory, + private val shadeDialogContextInteractor: ShadeDialogContextInteractor, ) { companion object { @@ -63,7 +64,8 @@ constructor( * Populate the dialog with information from and adapter obtained from * [userDetailViewAdapterProvider] and show it as launched from [expandable]. */ - fun showDialog(context: Context, expandable: Expandable) { + fun showDialog(expandable: Expandable) { + val context = shadeDialogContextInteractor.context with(dialogFactory.create(context)) { setShowForAllUsers(true) setCanceledOnTouchOutside(true) diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java index a7b51faaed57..10ac2cf76763 100644 --- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java @@ -16,6 +16,8 @@ package com.android.systemui.scrim; +import static com.android.systemui.Flags.notificationShadeBlur; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; @@ -214,8 +216,7 @@ public class ScrimDrawable extends Drawable { public void draw(@NonNull Canvas canvas) { mPaint.setColor(mMainColor); mPaint.setAlpha(mAlpha); - if (WindowBlurFlag.isEnabled()) { - // TODO(b/370555223): Match the alpha to the visual spec when it is finalized. + if (notificationShadeBlur() || WindowBlurFlag.isEnabled()) { // TODO (b/381263600), wire this at ScrimController, move it to PrimaryBouncerTransition mPaint.setAlpha((int) (0.5f * mAlpha)); } diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java index 4bfa61e9dcd4..0f80e7432a54 100644 --- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java +++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java @@ -16,6 +16,8 @@ package com.android.systemui.scrim; +import static com.android.systemui.Flags.notificationShadeBlur; + import static java.lang.Float.isNaN; import android.annotation.NonNull; @@ -39,13 +41,12 @@ import androidx.core.graphics.ColorUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.colorextraction.ColorExtractor; +import com.android.systemui.res.R; import com.android.systemui.shade.TouchLogger; import com.android.systemui.util.LargeScreenUtils; import java.util.concurrent.Executor; -import static com.android.systemui.Flags.notificationShadeBlur; - /** * A view which can draw a scrim. This view maybe be used in multiple windows running on different * threads, but is controlled by {@link com.android.systemui.statusbar.phone.ScrimController} so we @@ -253,8 +254,11 @@ public class ScrimView extends View { mainTinted = ColorUtils.blendARGB(mColors.getMainColor(), mTintColor, tintAmount); } if (notificationShadeBlur()) { - // TODO(b/370555223): Fix color and transparency to match visual spec exactly - mainTinted = ColorUtils.blendARGB(mColors.getMainColor(), Color.GRAY, 0.5f); + int layerAbove = ColorUtils.setAlphaComponent( + getResources().getColor(R.color.shade_panel, null), + (int) (0.4f * 255)); + int layerBelow = ColorUtils.setAlphaComponent(Color.WHITE, (int) (0.1f * 255)); + mainTinted = ColorUtils.compositeColors(layerAbove, layerBelow); } drawable.setColor(mainTinted, animated); } else { diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt index e1631ccdcb06..bbb13d5c1dfe 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt @@ -61,9 +61,18 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider { /** Callback for notifying of changes. */ @WeaklyReferencedCallback interface Callback { - /** Notifies that the current user will be changed. */ + /** + * Same as {@link onBeforeUserSwitching(Int, Runnable)} but the callback will be called + * automatically after the completion of this method. + */ fun onBeforeUserSwitching(newUser: Int) {} + /** Notifies that the current user will be changed. */ + fun onBeforeUserSwitching(newUser: Int, resultCallback: Runnable) { + onBeforeUserSwitching(newUser) + resultCallback.run() + } + /** * Same as {@link onUserChanging(Int, Context, Runnable)} but the callback will be called * automatically after the completion of this method. diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt index b7a3aedc565e..42d83637ec1a 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt @@ -196,8 +196,9 @@ internal constructor( private fun registerUserSwitchObserver() { iActivityManager.registerUserSwitchObserver( object : UserSwitchObserver() { - override fun onBeforeUserSwitching(newUserId: Int) { + override fun onBeforeUserSwitching(newUserId: Int, reply: IRemoteCallback?) { handleBeforeUserSwitching(newUserId) + reply?.sendResult(null) } override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) { @@ -236,8 +237,7 @@ internal constructor( setUserIdInternal(newUserId) notifySubscribers { callback, resultCallback -> - callback.onBeforeUserSwitching(newUserId) - resultCallback.run() + callback.onBeforeUserSwitching(newUserId, resultCallback) } .await() } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index f2c39063c867..839d4596bb7c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -48,7 +48,6 @@ import com.android.systemui.flags.FeatureFlagsClassic; import com.android.systemui.flags.Flags; import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; -import com.android.systemui.keyguard.MigrateClocksToBlueprint; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.model.Edge; import com.android.systemui.keyguard.shared.model.KeyguardState; @@ -367,9 +366,7 @@ public class NotificationShadeWindowViewController implements Dumpable { mTouchActive = true; mTouchCancelled = false; mDownEvent = ev; - if (MigrateClocksToBlueprint.isEnabled()) { - mService.userActivity(); - } + mService.userActivity(); } else if (ev.getActionMasked() == MotionEvent.ACTION_UP || ev.getActionMasked() == MotionEvent.ACTION_CANCEL) { mTouchActive = false; @@ -443,8 +440,7 @@ public class NotificationShadeWindowViewController implements Dumpable { float x = ev.getRawX(); float y = ev.getRawY(); if (mStatusBarViewController.touchIsWithinView(x, y)) { - if (!(MigrateClocksToBlueprint.isEnabled() - && mPrimaryBouncerInteractor.isBouncerShowing())) { + if (!mPrimaryBouncerInteractor.isBouncerShowing()) { if (mStatusBarWindowStateController.windowIsShowing()) { mIsTrackingBarGesture = true; return logDownDispatch(ev, "sending touch to status bar", @@ -453,7 +449,7 @@ public class NotificationShadeWindowViewController implements Dumpable { return logDownDispatch(ev, "hidden or hiding", true); } } else { - mShadeLogger.d("NSWVC: bouncer not showing"); + mShadeLogger.d("NSWVC: bouncer showing"); } } else { mShadeLogger.d("NSWVC: touch not within view"); @@ -511,34 +507,24 @@ public class NotificationShadeWindowViewController implements Dumpable { && !bouncerShowing && !mStatusBarStateController.isDozing()) { if (mDragDownHelper.isDragDownEnabled()) { - if (MigrateClocksToBlueprint.isEnabled()) { - // When on lockscreen, if the touch originates at the top of the screen - // go directly to QS and not the shade - if (mStatusBarStateController.getState() == KEYGUARD - && mQuickSettingsController.shouldQuickSettingsIntercept( - ev.getX(), ev.getY(), 0)) { - mShadeLogger.d("NSWVC: QS intercepted"); - return true; - } + // When on lockscreen, if the touch originates at the top of the screen go + // directly to QS and not the shade + if (mStatusBarStateController.getState() == KEYGUARD + && mQuickSettingsController.shouldQuickSettingsIntercept( + ev.getX(), ev.getY(), 0)) { + mShadeLogger.d("NSWVC: QS intercepted"); + return true; } // This handles drag down over lockscreen boolean result = mDragDownHelper.onInterceptTouchEvent(ev); - if (MigrateClocksToBlueprint.isEnabled()) { - if (result) { - mLastInterceptWasDragDownHelper = true; - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - mShadeLogger.d("NSWVC: drag down helper intercepted"); - } - } else if (didNotificationPanelInterceptEvent(ev)) { - return true; - } - } else { - if (result) { - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - mShadeLogger.d("NSWVC: drag down helper intercepted"); - } + if (result) { + mLastInterceptWasDragDownHelper = true; + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + mShadeLogger.d("NSWVC: drag down helper intercepted"); } + } else if (didNotificationPanelInterceptEvent(ev)) { + return true; } return result; } else { @@ -547,12 +533,10 @@ public class NotificationShadeWindowViewController implements Dumpable { return true; } } - } else if (MigrateClocksToBlueprint.isEnabled()) { + } else if (!bouncerShowing && didNotificationPanelInterceptEvent(ev)) { // This final check handles swipes on HUNs and when Pulsing - if (!bouncerShowing && didNotificationPanelInterceptEvent(ev)) { - mShadeLogger.d("NSWVC: intercepted for HUN/PULSING"); - return true; - } + mShadeLogger.d("NSWVC: intercepted for HUN/PULSING"); + return true; } return false; } @@ -562,9 +546,6 @@ public class NotificationShadeWindowViewController implements Dumpable { MotionEvent cancellation = MotionEvent.obtain(ev); cancellation.setAction(MotionEvent.ACTION_CANCEL); mStackScrollLayout.onInterceptTouchEvent(cancellation); - if (!MigrateClocksToBlueprint.isEnabled()) { - mShadeViewController.handleExternalInterceptTouch(cancellation); - } cancellation.recycle(); } @@ -574,22 +555,12 @@ public class NotificationShadeWindowViewController implements Dumpable { if (mStatusBarStateController.isDozing()) { handled = !mDozeServiceHost.isPulsing(); } - if (MigrateClocksToBlueprint.isEnabled()) { - if (mLastInterceptWasDragDownHelper && (mDragDownHelper.isDraggingDown())) { - // we still want to finish our drag down gesture when locking the screen - handled |= mDragDownHelper.onTouchEvent(ev) || handled; - } - if (!handled && mShadeViewController.handleExternalTouch(ev)) { - return true; - } - } else { - if (mDragDownHelper.isDragDownEnabled() - || mDragDownHelper.isDraggingDown()) { - // we still want to finish our drag down gesture when locking the screen - return mDragDownHelper.onTouchEvent(ev) || handled; - } else { - return handled; - } + if (mLastInterceptWasDragDownHelper && (mDragDownHelper.isDraggingDown())) { + // we still want to finish our drag down gesture when locking the screen + handled |= mDragDownHelper.onTouchEvent(ev) || handled; + } + if (!handled && mShadeViewController.handleExternalTouch(ev)) { + return true; } return handled; } @@ -673,14 +644,12 @@ public class NotificationShadeWindowViewController implements Dumpable { } private boolean didNotificationPanelInterceptEvent(MotionEvent ev) { - if (MigrateClocksToBlueprint.isEnabled()) { - // Since NotificationStackScrollLayout is now a sibling of notification_panel, we need - // to also ask NotificationPanelViewController directly, in order to process swipe up - // events originating from notifications - if (mShadeViewController.handleExternalInterceptTouch(ev)) { - mShadeLogger.d("NSWVC: NPVC intercepted"); - return true; - } + // Since NotificationStackScrollLayout is now a sibling of notification_panel, we need to + // also ask NotificationPanelViewController directly, in order to process swipe up events + // originating from notifications + if (mShadeViewController.handleExternalInterceptTouch(ev)) { + mShadeLogger.d("NSWVC: NPVC intercepted"); + return true; } return false; @@ -707,9 +676,7 @@ public class NotificationShadeWindowViewController implements Dumpable { if (!SceneContainerFlag.isEnabled()) { mAmbientState.setSwipingUp(false); } - if (MigrateClocksToBlueprint.isEnabled()) { - mDragDownHelper.stopDragging(); - } + mDragDownHelper.stopDragging(); } private void setBrightnessMirrorShowingForDepth(boolean showing) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt index 207439e1f374..58111576574e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt @@ -21,7 +21,6 @@ import android.view.ViewGroup import android.view.WindowInsets import androidx.annotation.VisibleForTesting import androidx.constraintlayout.widget.ConstraintSet -import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.END import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START @@ -32,7 +31,6 @@ import com.android.systemui.customization.R as customR import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.fragments.FragmentService -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.navigationbar.NavigationModeController import com.android.systemui.plugins.qs.QS @@ -275,7 +273,6 @@ constructor( constraintSet.clone(mView) setKeyguardStatusViewConstraints(constraintSet) setQsConstraints(constraintSet) - setNotificationsConstraints(constraintSet) setLargeScreenShadeHeaderConstraints(constraintSet) mView.applyConstraints(constraintSet) } @@ -288,21 +285,6 @@ constructor( } } - private fun setNotificationsConstraints(constraintSet: ConstraintSet) { - if (MigrateClocksToBlueprint.isEnabled) { - return - } - val startConstraintId = if (splitShadeEnabled) R.id.qs_edge_guideline else PARENT_ID - val nsslId = R.id.notification_stack_scroller - constraintSet.apply { - connect(nsslId, START, startConstraintId, START) - setMargin(nsslId, START, if (splitShadeEnabled) 0 else panelMarginHorizontal) - setMargin(nsslId, END, panelMarginHorizontal) - setMargin(nsslId, TOP, topMargin) - setMargin(nsslId, BOTTOM, notificationsBottomMargin) - } - } - private fun setQsConstraints(constraintSet: ConstraintSet) { val endConstraintId = if (splitShadeEnabled) R.id.qs_edge_guideline else PARENT_ID constraintSet.apply { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java index 13330553b2de..000a666bac0d 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java @@ -21,7 +21,6 @@ import static androidx.constraintlayout.core.widgets.Optimizer.OPTIMIZATION_GRAP import android.app.Fragment; import android.content.Context; import android.content.res.Configuration; -import android.graphics.Canvas; import android.graphics.Rect; import android.util.AttributeSet; import android.view.MotionEvent; @@ -33,13 +32,10 @@ import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintSet; import com.android.systemui.fragments.FragmentHostManager.FragmentListener; -import com.android.systemui.keyguard.MigrateClocksToBlueprint; import com.android.systemui.plugins.qs.QS; import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.AboveShelfObserver; -import java.util.ArrayList; -import java.util.Comparator; import java.util.function.Consumer; /** @@ -50,11 +46,7 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout private View mQsFrame; private View mStackScroller; - private View mKeyguardStatusBar; - private final ArrayList<View> mDrawingOrderedChildren = new ArrayList<>(); - private final ArrayList<View> mLayoutDrawingOrder = new ArrayList<>(); - private final Comparator<View> mIndexComparator = Comparator.comparingInt(this::indexOfChild); private Consumer<WindowInsets> mInsetsChangedListener = insets -> {}; private Consumer<QS> mQSFragmentAttachedListener = qs -> {}; private QS mQs; @@ -80,7 +72,6 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout protected void onFinishInflate() { super.onFinishInflate(); mQsFrame = findViewById(R.id.qs_frame); - mKeyguardStatusBar = findViewById(R.id.keyguard_header); } void setStackScroller(View stackScroller) { @@ -160,46 +151,11 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout } @Override - protected void dispatchDraw(Canvas canvas) { - mDrawingOrderedChildren.clear(); - mLayoutDrawingOrder.clear(); - if (mKeyguardStatusBar.getVisibility() == View.VISIBLE) { - mDrawingOrderedChildren.add(mKeyguardStatusBar); - mLayoutDrawingOrder.add(mKeyguardStatusBar); - } - if (mQsFrame.getVisibility() == View.VISIBLE) { - mDrawingOrderedChildren.add(mQsFrame); - mLayoutDrawingOrder.add(mQsFrame); - } - if (mStackScroller.getVisibility() == View.VISIBLE) { - mDrawingOrderedChildren.add(mStackScroller); - mLayoutDrawingOrder.add(mStackScroller); - } - - // Let's now find the order that the view has when drawing regularly by sorting - mLayoutDrawingOrder.sort(mIndexComparator); - super.dispatchDraw(canvas); - } - - @Override public boolean dispatchTouchEvent(MotionEvent ev) { return TouchLogger.logDispatchTouch("NotificationsQuickSettingsContainer", ev, super.dispatchTouchEvent(ev)); } - @Override - protected boolean drawChild(Canvas canvas, View child, long drawingTime) { - if (MigrateClocksToBlueprint.isEnabled()) { - return super.drawChild(canvas, child, drawingTime); - } - int layoutIndex = mLayoutDrawingOrder.indexOf(child); - if (layoutIndex >= 0) { - return super.drawChild(canvas, mDrawingOrderedChildren.get(layoutIndex), drawingTime); - } else { - return super.drawChild(canvas, child, drawingTime); - } - } - public void applyConstraints(ConstraintSet constraintSet) { constraintSet.applyTo(this); } diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java index 0df2299eb8dd..4fb43fdcfdae 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java @@ -68,7 +68,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.fragments.FragmentHostManager; -import com.android.systemui.keyguard.MigrateClocksToBlueprint; import com.android.systemui.media.controls.domain.pipeline.MediaDataManager; import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager; import com.android.systemui.plugins.FalsingManager; @@ -1828,16 +1827,6 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum "onQsIntercept: down action, QS partially expanded/collapsed"); return true; } - // TODO (b/265193930): remove dependency on NPVC - if (mPanelViewControllerLazy.get().isKeyguardShowing() - && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) { - // Dragging down on the lockscreen statusbar should prohibit other interactions - // immediately, otherwise we'll wait on the touchslop. This is to allow - // dragging down to expanded quick settings directly on the lockscreen. - if (!MigrateClocksToBlueprint.isEnabled()) { - mPanelView.getParent().requestDisallowInterceptTouchEvent(true); - } - } if (mExpansionAnimator != null) { mInitialHeightOnTouch = mExpansionHeight; mShadeLog.logMotionEvent(event, @@ -1879,9 +1868,6 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum && Math.abs(h) > Math.abs(x - mInitialTouchX) && shouldQuickSettingsIntercept( mInitialTouchX, mInitialTouchY, h)) { - if (!MigrateClocksToBlueprint.isEnabled()) { - mPanelView.getParent().requestDisallowInterceptTouchEvent(true); - } mShadeLog.onQsInterceptMoveQsTrackingEnabled(h); setTracking(true); traceQsJank(true, false); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt index d31868ca0217..61b9f0819f56 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt @@ -35,6 +35,8 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.res.R import com.android.systemui.scene.ui.view.WindowRootView import com.android.systemui.shade.data.repository.MutableShadeDisplaysRepository +import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor +import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractorImpl import com.android.systemui.shade.data.repository.ShadeDisplaysRepository import com.android.systemui.shade.data.repository.ShadeDisplaysRepositoryImpl import com.android.systemui.shade.display.ShadeDisplayPolicyModule @@ -216,6 +218,25 @@ object ShadeDisplayAwareModule { } @Provides + @SysUISingleton + fun provideShadeDialogContextInteractor( + impl: ShadeDialogContextInteractorImpl + ): ShadeDialogContextInteractor = impl + + @Provides + @IntoMap + @ClassKey(ShadeDialogContextInteractor::class) + fun provideShadeDialogContextInteractorCoreStartable( + impl: Provider<ShadeDialogContextInteractorImpl> + ): CoreStartable { + return if (ShadeWindowGoesAround.isEnabled) { + impl.get() + } else { + CoreStartable.NOP + } + } + + @Provides @IntoMap @ClassKey(ShadePrimaryDisplayCommand::class) fun provideShadePrimaryDisplayCommand( diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/FakeShadeDialogContextInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/FakeShadeDialogContextInteractor.kt new file mode 100644 index 000000000000..455370c726a2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/FakeShadeDialogContextInteractor.kt @@ -0,0 +1,23 @@ +/* + * 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.shade.domain.interactor + +import android.content.Context + +/** Fake context repository that always returns the same context. */ +class FakeShadeDialogContextInteractor(override val context: Context) : + ShadeDialogContextInteractor diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt new file mode 100644 index 000000000000..201dc0339a0a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt @@ -0,0 +1,97 @@ +/* + * 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.shade.domain.interactor + +import android.content.Context +import android.util.Log +import android.view.Display +import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL +import com.android.app.tracing.coroutines.launchTraced +import com.android.app.tracing.traceSection +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository +import com.android.systemui.shade.data.repository.ShadeDisplaysRepository +import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround +import javax.inject.Inject +import javax.inject.Provider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.filter + +/** Provides the correct context to show dialogs on the shade window, whenever it moves. */ +interface ShadeDialogContextInteractor { + /** Context usable to create dialogs on the notification shade display. */ + val context: Context +} + +@SysUISingleton +class ShadeDialogContextInteractorImpl +@Inject +constructor( + @Main private val defaultContext: Context, + private val displayWindowPropertyRepository: Provider<DisplayWindowPropertiesRepository>, + private val shadeDisplaysRepository: ShadeDisplaysRepository, + @Background private val bgScope: CoroutineScope, +) : CoreStartable, ShadeDialogContextInteractor { + + override fun start() { + if (ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()) return + bgScope.launchTraced(TAG) { + shadeDisplaysRepository.displayId + // No need for default display pre-warming. + .filter { it != Display.DEFAULT_DISPLAY } + .collectLatest { displayId -> + // Prewarms the context in the background every time the display changes. + // In this way, there will be no main thread delays when a dialog is shown. + getContextOrDefault(displayId) + } + } + } + + override val context: Context + get() { + if (!ShadeWindowGoesAround.isEnabled) { + return defaultContext + } + val displayId = shadeDisplaysRepository.displayId.value + return getContextOrDefault(displayId) + } + + private fun getContextOrDefault(displayId: Int): Context { + return try { + traceSection({ "Getting dialog context for displayId=$displayId" }) { + displayWindowPropertyRepository.get().get(displayId, DIALOG_WINDOW_TYPE).context + } + } catch (e: Exception) { + // This can happen if the display was disconnected in the meantime. + Log.e( + TAG, + "Couldn't get dialog context for displayId=$displayId. Returning default one", + e, + ) + defaultContext + } + } + + private companion object { + const val TAG = "ShadeDialogContextRepo" + const val DIALOG_WINDOW_TYPE = TYPE_STATUS_BAR_SUB_PANEL + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index b2ca33a4aecf..a7ad46296e08 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -44,13 +44,11 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.jank.InteractionJankMonitor.Configuration; import com.android.internal.logging.UiEventLogger; -import com.android.keyguard.KeyguardClockSwitch; import com.android.systemui.DejankUtils; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor; import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus; -import com.android.systemui.keyguard.MigrateClocksToBlueprint; import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; @@ -136,7 +134,6 @@ public class StatusBarStateControllerImpl implements private HistoricalState[] mHistoricalRecords = new HistoricalState[HISTORY_SIZE]; // These views are used by InteractionJankMonitor to get callback from HWUI. private View mView; - private KeyguardClockSwitch mClockSwitchView; /** * If any of the system bars is hidden. @@ -426,7 +423,6 @@ public class StatusBarStateControllerImpl implements if ((mView == null || !mView.isAttachedToWindow()) && (view != null && view.isAttachedToWindow())) { mView = view; - mClockSwitchView = view.findViewById(R.id.keyguard_clock_container); } mDozeAmountTarget = dozeAmount; if (animated) { @@ -511,16 +507,7 @@ public class StatusBarStateControllerImpl implements /** Returns the id of the currently rendering clock */ public String getClockId() { - if (MigrateClocksToBlueprint.isEnabled()) { - return mKeyguardClockInteractorLazy.get().getRenderedClockId(); - } - - if (mClockSwitchView == null) { - Log.e(TAG, "Clock container was missing"); - return KeyguardClockSwitch.MISSING_CLOCK_ID; - } - - return mClockSwitchView.getClockId(); + return mKeyguardClockInteractorLazy.get().getRenderedClockId(); } private void beginInteractionJankMonitor() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt index 66af275bc702..a7dbb47bc609 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.chips.notification.ui.viewmodel import android.view.View +import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor @@ -99,6 +100,17 @@ constructor( ) } + if (Flags.promoteNotificationsAutomatically()) { + // When we're promoting notifications automatically, the `when` time set on the + // notification will likely just be set to the current time, which would cause the chip + // to always show "now". We don't want early testers to get that experience since it's + // not what will happen at launch, so just don't show any time. + // TODO(b/364653005): Only ignore the `when` time if the notification was + // *automatically* promoted (as opposed to being legitimately promoted by the + // criteria). We'll need to track that status somehow. + return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener) + } + if (this.promotedContent.time == null) { return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/StatusBarPopupChips.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/StatusBarPopupChips.kt new file mode 100644 index 000000000000..9f523fc845ab --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/StatusBarPopupChips.kt @@ -0,0 +1,61 @@ +/* + * 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.chips.notification.shared + +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the status bar popup chips flag state. */ +@Suppress("NOTHING_TO_INLINE") +object StatusBarPopupChips { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_STATUS_BAR_POPUP_CHIPS + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the refactor enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.statusBarPopupChips() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is not enabled to ensure that the refactor author catches issues in testing. + * Caution!! Using this check incorrectly will cause crashes in nextfood builds! + */ + @JvmStatic + inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt new file mode 100644 index 000000000000..1663aebd7287 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt @@ -0,0 +1,49 @@ +/* + * 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.featurepods.popups.shared.model + +import com.android.systemui.common.shared.model.Icon + +/** + * Ids used to track different types of popup chips. Will be used to ensure only one chip is + * displaying its popup at a time. + */ +sealed class PopupChipId(val value: String) { + data object MediaControls : PopupChipId("MediaControls") +} + +/** Model for individual status bar popup chips. */ +sealed class PopupChipModel { + abstract val logName: String + abstract val chipId: PopupChipId + + data class Hidden(override val chipId: PopupChipId, val shouldAnimate: Boolean = true) : + PopupChipModel() { + override val logName = "Hidden(id=$chipId, anim=$shouldAnimate)" + } + + data class Shown( + override val chipId: PopupChipId, + val icon: Icon, + val chipText: String, + val isToggled: Boolean = false, + val onToggle: () -> Unit, + val onIconPressed: () -> Unit, + ) : PopupChipModel() { + override val logName = "Shown(id=$chipId, toggled=$isToggled)" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipViewModel.kt new file mode 100644 index 000000000000..5712be30ccd6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipViewModel.kt @@ -0,0 +1,29 @@ +/* + * 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.featurepods.popups.ui.viewmodel + +import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel +import kotlinx.coroutines.flow.StateFlow + +/** + * Interface for a view model that knows the display requirements for a single type of status bar + * popup chip. + */ +interface StatusBarPopupChipViewModel { + /** A flow modeling the popup chip that should be shown (or not shown). */ + val chip: StateFlow<PopupChipModel> +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt new file mode 100644 index 000000000000..b390f29b166c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.featurepods.popups.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipId +import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +/** + * View model deciding which system process chips to show in the status bar. Emits a list of + * PopupChipModels. + */ +@SysUISingleton +class StatusBarPopupChipsViewModel @Inject constructor(@Background scope: CoroutineScope) { + private data class PopupChipBundle( + val media: PopupChipModel = PopupChipModel.Hidden(chipId = PopupChipId.MediaControls) + ) + + private val incomingPopupChipBundle: Flow<PopupChipBundle?> = + flowOf(null).stateIn(scope, SharingStarted.Lazily, PopupChipBundle()) + + val popupChips: Flow<List<PopupChipModel>> = + incomingPopupChipBundle + .map { _ -> listOf(null).filterIsInstance<PopupChipModel.Shown>() } + .stateIn(scope, SharingStarted.Lazily, emptyList()) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java index 80e8f55b897a..d83acf34ca99 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.collection.inflation; import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE; +import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC; @@ -186,6 +187,9 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC); if (AsyncHybridViewInflation.isEnabled()) { params.markContentViewsFreeable(FLAG_CONTENT_VIEW_SINGLE_LINE); + if (LockscreenOtpRedaction.isSingleLineViewEnabled()) { + params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE); + } } mRowContentBindStage.requestRebind(entry, null); } @@ -256,10 +260,10 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { params.requireContentViews(FLAG_CONTENT_VIEW_EXPANDED); params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); params.setUseMinimized(isMinimized); - // TODO b/358403414: use the different types of redaction - boolean needsRedaction = inflaterParams.getRedactionType() != REDACTION_TYPE_NONE; + int redactionType = inflaterParams.getRedactionType(); - if (needsRedaction) { + params.setRedactionType(redactionType); + if (redactionType != REDACTION_TYPE_NONE) { params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC); } else { params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC); @@ -276,8 +280,8 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { } if (LockscreenOtpRedaction.isSingleLineViewEnabled()) { - - if (inflaterParams.isChildInGroup() && needsRedaction) { + if (inflaterParams.isChildInGroup() + && redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) { params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE); } else { params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index 8a1371f1c415..aa010cf63d5b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -62,6 +62,7 @@ import com.android.systemui.statusbar.notification.data.NotificationDataLayerMod import com.android.systemui.statusbar.notification.domain.NotificationDomainLayerModule; import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor; import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule; +import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.notification.icon.ConversationIconManager; import com.android.systemui.statusbar.notification.icon.IconManager; import com.android.systemui.statusbar.notification.init.NotificationsController; @@ -78,8 +79,7 @@ import com.android.systemui.statusbar.notification.logging.NotificationPanelLogg import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerImpl; import com.android.systemui.statusbar.notification.logging.dagger.NotificationsLogModule; import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor; -import com.android.systemui.statusbar.notification.promoted.PromotedNotificationLogger; -import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsProvider; +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractorImpl; import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel; import com.android.systemui.statusbar.notification.row.NotificationEntryProcessorFactory; import com.android.systemui.statusbar.notification.row.NotificationEntryProcessorFactoryLooperImpl; @@ -92,7 +92,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter; -import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.policy.ZenModesCleanupStartable; import dagger.Binds; @@ -105,8 +104,6 @@ import kotlin.coroutines.CoroutineContext; import kotlinx.coroutines.CoroutineScope; -import java.util.Optional; - import javax.inject.Provider; /** @@ -315,21 +312,17 @@ public interface NotificationsModule { @ClassKey(ZenModesCleanupStartable.class) CoreStartable bindsZenModesCleanup(ZenModesCleanupStartable zenModesCleanup); - /** - * Provides {@link - * com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor} if - * one of the relevant feature flags is enabled. - */ + /** Provides the default implementation of {@link PromotedNotificationContentExtractor} if at + * least one of the relevant feature flags is enabled, or an implementation that always returns + * null if none are enabled. */ @Provides @SysUISingleton - static Optional<PromotedNotificationContentExtractor> - providePromotedNotificationContentExtractor( - PromotedNotificationsProvider provider, Context context, - PromotedNotificationLogger logger) { + static PromotedNotificationContentExtractor providesPromotedNotificationContentExtractor( + Provider<PromotedNotificationContentExtractorImpl> implProvider) { if (PromotedNotificationContentModel.featureFlagEnabled()) { - return Optional.of(new PromotedNotificationContentExtractor(provider, context, logger)); + return implProvider.get(); } else { - return Optional.empty(); + return (entry, recoveredBuilder) -> null; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java index 6756077e5444..d02e17cab534 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java @@ -1299,7 +1299,6 @@ public class HeadsUpManagerImpl } private NotificationEntry requireEntry() { - /* check if */ SceneContainerFlag.isUnexpectedlyInLegacyMode(); return Objects.requireNonNull(mEntry); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt index 863c665eb4f5..4e9e3336b86f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt @@ -34,15 +34,22 @@ import com.android.systemui.statusbar.notification.promoted.shared.model.Promote import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.When import javax.inject.Inject +interface PromotedNotificationContentExtractor { + fun extractContent( + entry: NotificationEntry, + recoveredBuilder: Notification.Builder, + ): PromotedNotificationContentModel? +} + @SysUISingleton -class PromotedNotificationContentExtractor +class PromotedNotificationContentExtractorImpl @Inject constructor( private val promotedNotificationsProvider: PromotedNotificationsProvider, @ShadeDisplayAware private val context: Context, private val logger: PromotedNotificationLogger, -) { - fun extractContent( +) : PromotedNotificationContentExtractor { + override fun extractContent( entry: NotificationEntry, recoveredBuilder: Notification.Builder, ): PromotedNotificationContentModel? { @@ -169,5 +176,5 @@ private fun CallStyle.extractContent(contentBuilder: PromotedNotificationContent private fun ProgressStyle.extractContent(contentBuilder: PromotedNotificationContentModel.Builder) { // TODO: Create NotificationProgressModel.toSkeleton, or something similar. - contentBuilder.progress = createProgressModel(0xffffffff.toInt(), 0x00000000) + contentBuilder.progress = createProgressModel(0xffffffff.toInt(), 0xff000000.toInt()) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt index 13ad1413e89d..a43f8dbc1b5d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.notification.promoted import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel.ERROR import com.android.systemui.log.core.LogLevel.INFO -import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel @@ -27,7 +26,7 @@ import javax.inject.Inject class PromotedNotificationLogger @Inject -constructor(@NotificationLog private val buffer: LogBuffer) { +constructor(@PromotedNotificationLog private val buffer: LogBuffer) { fun logExtractionSkipped(entry: NotificationEntry, reason: String) { buffer.log( EXTRACTION_TAG, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt new file mode 100644 index 000000000000..0f21514fcc94 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt @@ -0,0 +1,34 @@ +/* + * 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.notification.promoted.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +@SysUISingleton +class AODPromotedNotificationInteractor +@Inject +constructor(activeNotificationsInteractor: ActiveNotificationsInteractor) { + val content: Flow<PromotedNotificationContentModel?> = + activeNotificationsInteractor.topLevelRepresentativeNotifications.map { notifs -> + notifs.firstNotNullOfOrNull { it.promotedContent } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt index fe2dabe1ba8a..74809fd8622f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt @@ -28,7 +28,7 @@ import com.android.systemui.statusbar.notification.promoted.PromotedNotification * like the skeleton view on AOD or the status bar chip. */ data class PromotedNotificationContentModel( - val key: String, + val identity: Identity, // for all styles: val skeletonSmallIcon: Icon?, // TODO(b/377568176): Make into an IconModel. @@ -82,7 +82,7 @@ data class PromotedNotificationContentModel( fun build() = PromotedNotificationContentModel( - key = key, + identity = Identity(key, style), skeletonSmallIcon = skeletonSmallIcon, appName = appName, subText = subText, @@ -103,6 +103,8 @@ data class PromotedNotificationContentModel( ) } + data class Identity(val key: String, val style: Style) + /** The timestamp associated with a notification, along with the mode used to display it. */ data class When(val time: Long, val mode: Mode) { /** The mode used to display a notification's `when` value. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/AODPromotedNotificationViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/AODPromotedNotificationViewModel.kt new file mode 100644 index 000000000000..adfa6a10814d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/AODPromotedNotificationViewModel.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.promoted.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.promoted.domain.interactor.AODPromotedNotificationInteractor +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Identity +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map + +@SysUISingleton +class AODPromotedNotificationViewModel +@Inject +constructor(interactor: AODPromotedNotificationInteractor) { + private val content: Flow<PromotedNotificationContentModel?> = interactor.content + private val identity: Flow<Identity?> = content.mapNonNullsKeepingNulls { it.identity } + + val notification: Flow<PromotedNotificationViewModel?> = + identity.distinctUntilChanged().mapNonNullsKeepingNulls { identity -> + val updates = interactor.content.filterNotNull().filter { it.identity == identity } + PromotedNotificationViewModel(identity, updates) + } +} + +private fun <T, R> Flow<T?>.mapNonNullsKeepingNulls(block: (T) -> R): Flow<R?> = map { + it?.let(block) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/PromotedNotificationViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/PromotedNotificationViewModel.kt new file mode 100644 index 000000000000..f265e0ff33f8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/PromotedNotificationViewModel.kt @@ -0,0 +1,58 @@ +/* + * 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.notification.promoted.ui.viewmodel + +import android.graphics.drawable.Icon +import com.android.internal.widget.NotificationProgressModel +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.When +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class PromotedNotificationViewModel( + identity: PromotedNotificationContentModel.Identity, + content: Flow<PromotedNotificationContentModel>, +) { + // for all styles: + + val key: String = identity.key + val style: Style = identity.style + + val skeletonSmallIcon: Flow<Icon?> = content.map { it.skeletonSmallIcon } + val appName: Flow<CharSequence?> = content.map { it.appName } + val subText: Flow<CharSequence?> = content.map { it.subText } + + private val time: Flow<When?> = content.map { it.time } + val whenTime: Flow<Long?> = time.map { it?.time } + val whenMode: Flow<When.Mode?> = time.map { it?.mode } + + val lastAudiblyAlertedMs: Flow<Long> = content.map { it.lastAudiblyAlertedMs } + val profileBadgeResId: Flow<Int?> = content.map { it.profileBadgeResId } + val title: Flow<CharSequence?> = content.map { it.title } + val text: Flow<CharSequence?> = content.map { it.text } + val skeletonLargeIcon: Flow<Icon?> = content.map { it.skeletonLargeIcon } + + // for CallStyle: + val personIcon: Flow<Icon?> = content.map { it.personIcon } + val personName: Flow<CharSequence?> = content.map { it.personName } + val verificationIcon: Flow<Icon?> = content.map { it.verificationIcon } + val verificationText: Flow<CharSequence?> = content.map { it.verificationText } + + // for ProgressStyle: + val progress: Flow<NotificationProgressModel?> = content.map { it.progress } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index 6e05e8e8b80e..70e27a981b49 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.row; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; +import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP; @@ -25,6 +26,7 @@ import static com.android.systemui.statusbar.notification.row.NotificationConten import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; +import android.app.Notification.MessagingStyle; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; @@ -161,9 +163,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder entry, mConversationProcessor, row, - bindParams.isMinimized, - bindParams.usesIncreasedHeight, - bindParams.usesIncreasedHeadsUpHeight, + bindParams, callback, mRemoteInputManager.getRemoteViewsOnClickHandler(), /* isMediaFlagEnabled = */ mIsMediaInQS, @@ -187,13 +187,13 @@ public class NotificationContentInflater implements NotificationRowContentBinder boolean inflateSynchronously, @InflationFlag int reInflateFlags, Notification.Builder builder, + Context systemUiContext, Context packageContext, SmartReplyStateInflater smartRepliesInflater) { InflationProgress result = createRemoteViews(reInflateFlags, builder, - bindParams.isMinimized, - bindParams.usesIncreasedHeight, - bindParams.usesIncreasedHeadsUpHeight, + bindParams, + systemUiContext, packageContext, row, mNotifLayoutInflaterFactoryProvider, @@ -203,18 +203,20 @@ public class NotificationContentInflater implements NotificationRowContentBinder result = inflateSmartReplyViews(result, reInflateFlags, entry, row.getContext(), packageContext, row.getExistingSmartReplyState(), smartRepliesInflater, mLogger); boolean isConversation = entry.getRanking().isConversation(); + Notification.MessagingStyle messagingStyle = null; + if (isConversation && (AsyncHybridViewInflation.isEnabled() + || LockscreenOtpRedaction.isSingleLineViewEnabled())) { + messagingStyle = mConversationProcessor + .processNotification(entry, builder, mLogger); + } if (AsyncHybridViewInflation.isEnabled()) { - Notification.MessagingStyle messagingStyle = null; - if (isConversation) { - messagingStyle = mConversationProcessor - .processNotification(entry, builder, mLogger); - } SingleLineViewModel viewModel = SingleLineViewInflater .inflateSingleLineViewModel( entry.getSbn().getNotification(), messagingStyle, builder, - row.getContext() + row.getContext(), + false ); // If the messagingStyle is null, we want to inflate the normal view isConversation = viewModel.isConversation(); @@ -228,11 +230,22 @@ public class NotificationContentInflater implements NotificationRowContentBinder mLogger ); } - if (LockscreenOtpRedaction.isSingleLineViewEnabled()) { - result.mPublicInflatedSingleLineViewModel = - SingleLineViewInflater.inflateRedactedSingleLineViewModel(row.getContext(), - isConversation); + if (bindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) { + result.mPublicInflatedSingleLineViewModel = + SingleLineViewInflater.inflateSingleLineViewModel( + entry.getSbn().getNotification(), + messagingStyle, + builder, + row.getContext(), + true); + } else { + result.mPublicInflatedSingleLineViewModel = + SingleLineViewInflater.inflateRedactedSingleLineViewModel( + row.getContext(), + isConversation + ); + } result.mPublicInflatedSingleLineView = SingleLineViewInflater.inflatePublicSingleLineView( isConversation, @@ -411,8 +424,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder } private static InflationProgress createRemoteViews(@InflationFlag int reInflateFlags, - Notification.Builder builder, boolean isMinimized, boolean usesIncreasedHeight, - boolean usesIncreasedHeadsUpHeight, Context packageContext, + Notification.Builder builder, BindParams bindParams, Context systemUiContext, + Context packageContext, ExpandableNotificationRow row, NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider, HeadsUpStyleProvider headsUpStyleProvider, @@ -423,13 +436,13 @@ public class NotificationContentInflater implements NotificationRowContentBinder if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) { logger.logAsyncTaskProgress(entryForLogging, "creating contracted remote view"); - result.newContentView = createContentView(builder, isMinimized, - usesIncreasedHeight); + result.newContentView = createContentView(builder, bindParams.isMinimized, + bindParams.usesIncreasedHeight); } if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) { logger.logAsyncTaskProgress(entryForLogging, "creating expanded remote view"); - result.newExpandedView = createExpandedView(builder, isMinimized); + result.newExpandedView = createExpandedView(builder, bindParams.isMinimized); } if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) { @@ -439,13 +452,20 @@ public class NotificationContentInflater implements NotificationRowContentBinder result.newHeadsUpView = builder.createCompactHeadsUpContentView(); } else { result.newHeadsUpView = builder.createHeadsUpContentView( - usesIncreasedHeadsUpHeight); + bindParams.usesIncreasedHeadsUpHeight); } } if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) { logger.logAsyncTaskProgress(entryForLogging, "creating public remote view"); - result.newPublicView = builder.makePublicContentView(isMinimized); + if (LockscreenOtpRedaction.isEnabled() + && bindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) { + result.newPublicView = createSensitiveContentMessageNotification( + row.getEntry().getSbn().getNotification(), builder.getStyle(), + systemUiContext, packageContext).createContentView(true); + } else { + result.newPublicView = builder.makePublicContentView(bindParams.isMinimized); + } } if (AsyncGroupHeaderViewInflation.isEnabled()) { @@ -473,6 +493,42 @@ public class NotificationContentInflater implements NotificationRowContentBinder }); } + private static Notification.Builder createSensitiveContentMessageNotification( + Notification original, + Notification.Style originalStyle, + Context systemUiContext, + Context packageContext) { + Notification.Builder redacted = + new Notification.Builder(packageContext, original.getChannelId()); + redacted.setContentTitle(original.extras.getCharSequence(Notification.EXTRA_TITLE)); + CharSequence redactedMessage = systemUiContext.getString( + R.string.redacted_notification_single_line_text + ); + + if (originalStyle instanceof MessagingStyle oldStyle) { + MessagingStyle newStyle = new MessagingStyle(oldStyle.getUser()); + newStyle.setConversationTitle(oldStyle.getConversationTitle()); + newStyle.setGroupConversation(false); + newStyle.setConversationType(oldStyle.getConversationType()); + newStyle.setShortcutIcon(oldStyle.getShortcutIcon()); + newStyle.setBuilder(redacted); + MessagingStyle.Message latestMessage = + MessagingStyle.findLatestIncomingMessage(oldStyle.getMessages()); + if (latestMessage != null) { + MessagingStyle.Message newMessage = new MessagingStyle.Message(redactedMessage, + latestMessage.getTimestamp(), latestMessage.getSenderPerson()); + newStyle.addMessage(newMessage); + } + redacted.setStyle(newStyle); + } else { + redacted.setContentText(redactedMessage); + } + redacted.setLargeIcon(original.getLargeIcon()); + redacted.setSmallIcon(original.getSmallIcon()); + return redacted; + } + + private static void setNotifsViewsInflaterFactory(InflationProgress result, ExpandableNotificationRow row, NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider) { @@ -921,7 +977,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder logger.logAsyncTaskProgress(entry, "finishing"); if (PromotedNotificationContentModel.featureFlagEnabled()) { - entry.setPromotedNotificationContentModel(result.mExtractedPromotedNotificationContent); + entry.setPromotedNotificationContentModel(result.mPromotedContent); } boolean setRepliesAndActions = true; @@ -1118,10 +1174,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder private final NotificationEntry mEntry; private final Context mContext; private final boolean mInflateSynchronously; - private final boolean mIsMinimized; - private final boolean mUsesIncreasedHeight; + private final BindParams mBindParams; private final InflationCallback mCallback; - private final boolean mUsesIncreasedHeadsUpHeight; private final @InflationFlag int mReInflateFlags; private final NotifRemoteViewCache mRemoteViewCache; private final Executor mInflationExecutor; @@ -1145,9 +1199,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder NotificationEntry entry, ConversationNotificationProcessor conversationProcessor, ExpandableNotificationRow row, - boolean isMinimized, - boolean usesIncreasedHeight, - boolean usesIncreasedHeadsUpHeight, + BindParams bindParams, InflationCallback callback, RemoteViews.InteractionHandler remoteViewClickHandler, boolean isMediaFlagEnabled, @@ -1164,9 +1216,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder mRemoteViewCache = cache; mSmartRepliesInflater = smartRepliesInflater; mContext = mRow.getContext(); - mIsMinimized = isMinimized; - mUsesIncreasedHeight = usesIncreasedHeight; - mUsesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight; + mBindParams = bindParams; mRemoteViewClickHandler = remoteViewClickHandler; mCallback = callback; mConversationProcessor = conversationProcessor; @@ -1236,8 +1286,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder mEntry, recoveredBuilder, mLogger); } InflationProgress inflationProgress = createRemoteViews(mReInflateFlags, - recoveredBuilder, mIsMinimized, mUsesIncreasedHeight, - mUsesIncreasedHeadsUpHeight, packageContext, mRow, + recoveredBuilder, mBindParams, mContext, packageContext, mRow, mNotifLayoutInflaterFactoryProvider, mHeadsUpStyleProvider, mLogger); mLogger.logAsyncTaskProgress(mEntry, @@ -1264,7 +1313,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder mEntry.getSbn().getNotification(), messagingStyle, recoveredBuilder, - mContext + mContext, + false ); result.mInflatedSingleLineView = SingleLineViewInflater.inflatePrivateSingleLineView( @@ -1277,9 +1327,22 @@ public class NotificationContentInflater implements NotificationRowContentBinder } if (LockscreenOtpRedaction.isSingleLineViewEnabled()) { - result.mPublicInflatedSingleLineViewModel = - SingleLineViewInflater.inflateRedactedSingleLineViewModel(mContext, - isConversation); + if (mBindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) { + result.mPublicInflatedSingleLineViewModel = + SingleLineViewInflater.inflateSingleLineViewModel( + mEntry.getSbn().getNotification(), + messagingStyle, + recoveredBuilder, + mContext, + true + ); + } else { + result.mPublicInflatedSingleLineViewModel = + SingleLineViewInflater.inflateRedactedSingleLineViewModel( + mContext, + isConversation + ); + } result.mPublicInflatedSingleLineView = SingleLineViewInflater.inflatePublicSingleLineView( isConversation, @@ -1292,10 +1355,13 @@ public class NotificationContentInflater implements NotificationRowContentBinder if (PromotedNotificationContentModel.featureFlagEnabled()) { mLogger.logAsyncTaskProgress(mEntry, "extracting promoted notification content"); - result.mExtractedPromotedNotificationContent = mPromotedNotificationContentExtractor - .extractContent(mEntry, recoveredBuilder); + final PromotedNotificationContentModel promotedContent = + mPromotedNotificationContentExtractor.extractContent(mEntry, + recoveredBuilder); mLogger.logAsyncTaskProgress(mEntry, "extracted promoted notification content: " - + result.mExtractedPromotedNotificationContent); + + promotedContent); + + result.mPromotedContent = promotedContent; } mLogger.logAsyncTaskProgress(mEntry, @@ -1317,7 +1383,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder mCancellationSignal = apply( mInflationExecutor, mInflateSynchronously, - mIsMinimized, + mBindParams.isMinimized, result, mReInflateFlags, mRemoteViewCache, @@ -1399,7 +1465,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder @VisibleForTesting static class InflationProgress { - PromotedNotificationContentModel mExtractedPromotedNotificationContent; + PromotedNotificationContentModel mPromotedContent; private RemoteViews newContentView; private RemoteViews newHeadsUpView; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java index 07384afe2d2e..1cef8791e0ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.row; +import static com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -141,20 +143,33 @@ public interface NotificationRowContentBinder { */ class BindParams { + public BindParams(boolean minimized, boolean increasedHeight, + boolean increasedHeadsUpHeight, int redaction) { + isMinimized = minimized; + usesIncreasedHeight = increasedHeight; + usesIncreasedHeadsUpHeight = increasedHeadsUpHeight; + redactionType = redaction; + } + /** * Bind a minimized version of the content views. */ - public boolean isMinimized; + public final boolean isMinimized; /** * Use increased height when binding contracted view. */ - public boolean usesIncreasedHeight; + public final boolean usesIncreasedHeight; /** * Use increased height when binding heads up views. */ - public boolean usesIncreasedHeadsUpHeight; + public final boolean usesIncreasedHeadsUpHeight; + + /** + * Controls the type of public view to show, if a public view is requested + */ + public final @RedactionType int redactionType; } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt index c7d80e9d03ce..c619b17f1ad8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.notification.row import android.annotation.SuppressLint -import android.app.Flags import android.app.Notification +import android.app.Notification.MessagingStyle import android.content.Context import android.content.ContextWrapper import android.content.pm.ApplicationInfo @@ -43,6 +43,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.NotifInflation import com.android.systemui.res.R import com.android.systemui.statusbar.InflationTask +import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT import com.android.systemui.statusbar.NotificationRemoteInputManager import com.android.systemui.statusbar.notification.ConversationNotificationProcessor import com.android.systemui.statusbar.notification.InflationException @@ -143,9 +144,7 @@ constructor( entry, conversationProcessor, row, - bindParams.isMinimized, - bindParams.usesIncreasedHeight, - bindParams.usesIncreasedHeadsUpHeight, + bindParams, callback, remoteInputManager.remoteViewsOnClickHandler, /* isMediaFlagEnabled = */ smartReplyStateInflater, @@ -179,10 +178,8 @@ constructor( reInflateFlags = reInflateFlags, entry = entry, builder = builder, - isMinimized = bindParams.isMinimized, - usesIncreasedHeight = bindParams.usesIncreasedHeight, - usesIncreasedHeadsUpHeight = bindParams.usesIncreasedHeadsUpHeight, - systemUIContext = systemUIContext, + bindParams, + systemUiContext = systemUIContext, packageContext = packageContext, row = row, notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider, @@ -371,9 +368,7 @@ constructor( private val entry: NotificationEntry, private val conversationProcessor: ConversationNotificationProcessor, private val row: ExpandableNotificationRow, - private val isMinimized: Boolean, - private val usesIncreasedHeight: Boolean, - private val usesIncreasedHeadsUpHeight: Boolean, + private val bindParams: BindParams, private val callback: InflationCallback?, private val remoteViewClickHandler: InteractionHandler?, private val smartRepliesInflater: SmartReplyStateInflater, @@ -441,10 +436,8 @@ constructor( reInflateFlags = reInflateFlags, entry = entry, builder = recoveredBuilder, - isMinimized = isMinimized, - usesIncreasedHeight = usesIncreasedHeight, - usesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight, - systemUIContext = context, + bindParams = bindParams, + systemUiContext = context, packageContext = packageContext, row = row, notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider, @@ -514,7 +507,7 @@ constructor( apply( inflationExecutor, inflateSynchronously, - isMinimized, + bindParams.isMinimized, progress, reInflateFlags, remoteViewCache, @@ -591,7 +584,7 @@ constructor( @VisibleForTesting val packageContext: Context, val remoteViews: NewRemoteViews, val contentModel: NotificationContentModel, - val extractedPromotedNotificationContentModel: PromotedNotificationContentModel?, + val promotedContent: PromotedNotificationContentModel?, ) { var inflatedContentView: View? = null @@ -671,10 +664,8 @@ constructor( @InflationFlag reInflateFlags: Int, entry: NotificationEntry, builder: Notification.Builder, - isMinimized: Boolean, - usesIncreasedHeight: Boolean, - usesIncreasedHeadsUpHeight: Boolean, - systemUIContext: Context, + bindParams: BindParams, + systemUiContext: Context, packageContext: Context, row: ExpandableNotificationRow, notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider, @@ -683,16 +674,15 @@ constructor( promotedNotificationContentExtractor: PromotedNotificationContentExtractor, logger: NotificationRowContentBinderLogger, ): InflationProgress { - val promoted = + val promotedContent = if (PromotedNotificationContentModel.featureFlagEnabled()) { logger.logAsyncTaskProgress(entry, "extracting promoted notification content") - val extracted = - promotedNotificationContentExtractor.extractContent(entry, builder) - logger.logAsyncTaskProgress( - entry, - "extracted promoted notification content: {extracted}", - ) - extracted + promotedNotificationContentExtractor.extractContent(entry, builder).also { + logger.logAsyncTaskProgress( + entry, + "extracted promoted notification content: $it", + ) + } } else { null } @@ -707,9 +697,10 @@ constructor( createRemoteViews( reInflateFlags = reInflateFlags, builder = builder, - isMinimized = isMinimized, - usesIncreasedHeight = usesIncreasedHeight, - usesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight, + bindParams = bindParams, + entry = entry, + systemUiContext = systemUiContext, + packageContext = packageContext, row = row, notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider, headsUpStyleProvider = headsUpStyleProvider, @@ -726,7 +717,8 @@ constructor( notification = entry.sbn.notification, messagingStyle = messagingStyle, builder = builder, - systemUiContext = systemUIContext, + systemUiContext = systemUiContext, + redactText = false, ) } else null @@ -736,10 +728,20 @@ constructor( reInflateFlags and FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE != 0 ) { logger.logAsyncTaskProgress(entry, "inflating public single line view model") - SingleLineViewInflater.inflateRedactedSingleLineViewModel( - systemUIContext, - entry.ranking.isConversation, - ) + if (bindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) { + SingleLineViewInflater.inflateSingleLineViewModel( + notification = entry.sbn.notification, + messagingStyle = messagingStyle, + builder = builder, + systemUiContext = systemUiContext, + redactText = true, + ) + } else { + SingleLineViewInflater.inflateRedactedSingleLineViewModel( + systemUiContext, + entry.ranking.isConversation, + ) + } } else null val headsUpStatusBarModel = @@ -759,16 +761,54 @@ constructor( packageContext = packageContext, remoteViews = remoteViews, contentModel = contentModel, - extractedPromotedNotificationContentModel = promoted, + promotedContent = promotedContent, ) } + private fun createSensitiveContentMessageNotification( + original: Notification, + originalStyle: Notification.Style?, + sysUiContext: Context, + packageContext: Context, + ): Notification.Builder { + val redacted = Notification.Builder(packageContext, original.channelId) + redacted.setContentTitle(original.extras.getCharSequence(Notification.EXTRA_TITLE)) + val redactedMessage = + sysUiContext.getString(R.string.redacted_notification_single_line_text) + + if (originalStyle is MessagingStyle) { + val newStyle = MessagingStyle(originalStyle.user) + newStyle.conversationTitle = originalStyle.conversationTitle + newStyle.isGroupConversation = false + newStyle.conversationType = originalStyle.conversationType + newStyle.shortcutIcon = originalStyle.shortcutIcon + newStyle.setBuilder(redacted) + val latestMessage = MessagingStyle.findLatestIncomingMessage(originalStyle.messages) + if (latestMessage != null) { + val newMessage = + MessagingStyle.Message( + redactedMessage, + latestMessage.timestamp, + latestMessage.senderPerson, + ) + newStyle.addMessage(newMessage) + } + redacted.style = newStyle + } else { + redacted.setContentText(redactedMessage) + } + redacted.setLargeIcon(original.getLargeIcon()) + redacted.setSmallIcon(original.smallIcon) + return redacted + } + private fun createRemoteViews( @InflationFlag reInflateFlags: Int, builder: Notification.Builder, - isMinimized: Boolean, - usesIncreasedHeight: Boolean, - usesIncreasedHeadsUpHeight: Boolean, + bindParams: BindParams, + entry: NotificationEntry, + systemUiContext: Context, + packageContext: Context, row: ExpandableNotificationRow, notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider, headsUpStyleProvider: HeadsUpStyleProvider, @@ -782,7 +822,11 @@ constructor( entryForLogging, "creating contracted remote view", ) - createContentView(builder, isMinimized, usesIncreasedHeight) + createContentView( + builder, + bindParams.isMinimized, + bindParams.usesIncreasedHeight, + ) } else null val expanded = if (reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0) { @@ -790,7 +834,7 @@ constructor( entryForLogging, "creating expanded remote view", ) - createExpandedView(builder, isMinimized) + createExpandedView(builder, bindParams.isMinimized) } else null val headsUp = if (reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0) { @@ -802,13 +846,26 @@ constructor( if (isHeadsUpCompact) { builder.createCompactHeadsUpContentView() } else { - builder.createHeadsUpContentView(usesIncreasedHeadsUpHeight) + builder.createHeadsUpContentView(bindParams.usesIncreasedHeadsUpHeight) } } else null val public = if (reInflateFlags and FLAG_CONTENT_VIEW_PUBLIC != 0) { logger.logAsyncTaskProgress(entryForLogging, "creating public remote view") - builder.makePublicContentView(isMinimized) + if ( + LockscreenOtpRedaction.isEnabled && + bindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT + ) { + createSensitiveContentMessageNotification( + entry.sbn.notification, + builder.style, + systemUiContext, + packageContext, + ) + .createContentView(bindParams.usesIncreasedHeight) + } else { + builder.makePublicContentView(bindParams.isMinimized) + } } else null val normalGroupHeader = if ( @@ -1420,8 +1477,7 @@ constructor( entry.setContentModel(result.contentModel) if (PromotedNotificationContentModel.featureFlagEnabled()) { - entry.promotedNotificationContentModel = - result.extractedPromotedNotificationContentModel + entry.promotedNotificationContentModel = result.promotedContent } result.inflatedSmartReplyState?.let { row.privateLayout.setInflatedSmartReplyState(it) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java index 427fb66ca2d0..bc44cb0e1074 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.row; +import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE; +import static com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; @@ -31,6 +33,7 @@ public final class RowContentBindParams { private boolean mUseIncreasedHeadsUpHeight; private boolean mViewsNeedReinflation; private @InflationFlag int mContentViews = DEFAULT_INFLATION_FLAGS; + private @RedactionType int mRedactionType = REDACTION_TYPE_NONE; /** * Content views that are out of date and need to be rebound. @@ -58,6 +61,20 @@ public final class RowContentBindParams { } /** + * @return What type of redaction should be used by the public view (if requested) + */ + public @RedactionType int getRedactionType() { + return mRedactionType; + } + + /** + * Set the redaction type, which controls what sort of public view is shown. + */ + public void setRedactionType(@RedactionType int redactionType) { + mRedactionType = redactionType; + } + + /** * Set whether content should use an increased height version of its contracted view. */ public void setUseIncreasedCollapsedHeight(boolean useIncreasedHeight) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java index 89fcda949b5b..53f74161e7fc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java @@ -72,10 +72,8 @@ public class RowContentBindStage extends BindStage<RowContentBindParams> { // Bind/unbind with parameters mBinder.unbindContent(entry, row, contentToUnbind); - BindParams bindParams = new BindParams(); - bindParams.isMinimized = params.useMinimized(); - bindParams.usesIncreasedHeight = params.useIncreasedHeight(); - bindParams.usesIncreasedHeadsUpHeight = params.useIncreasedHeadsUpHeight(); + BindParams bindParams = new BindParams(params.useMinimized(), params.useIncreasedHeight(), + params.useIncreasedHeadsUpHeight(), params.getRedactionType()); boolean forceInflate = params.needsReinflation(); InflationCallback inflationCallback = new InflationCallback() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt index e702f10d7f50..fe2803bfc5d6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt @@ -51,6 +51,7 @@ internal object SingleLineViewInflater { * notification, not for legacy messaging notifications * @param builder the recovered Notification Builder * @param systemUiContext the context of Android System UI + * @param redactText indicates if the text needs to be redacted * @return the inflated SingleLineViewModel */ @JvmStatic @@ -59,13 +60,21 @@ internal object SingleLineViewInflater { messagingStyle: MessagingStyle?, builder: Notification.Builder, systemUiContext: Context, + redactText: Boolean, ): SingleLineViewModel { if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) { return SingleLineViewModel(null, null, null) } peopleHelper.init(systemUiContext) var titleText = HybridGroupManager.resolveTitle(notification) - var contentText = HybridGroupManager.resolveText(notification) + var contentText = + if (redactText) { + systemUiContext.getString( + com.android.systemui.res.R.string.redacted_notification_single_line_text + ) + } else { + HybridGroupManager.resolveText(notification) + } if (messagingStyle == null) { return SingleLineViewModel( @@ -81,7 +90,7 @@ internal object SingleLineViewInflater { if (conversationTextData?.conversationTitle?.isNotEmpty() == true) { titleText = conversationTextData.conversationTitle } - if (conversationTextData?.conversationText?.isNotEmpty() == true) { + if (!redactText && conversationTextData?.conversationText?.isNotEmpty() == true) { contentText = conversationTextData.conversationText } 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 7c9d850eaf07..38a70359e816 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 @@ -3729,14 +3729,6 @@ public class NotificationStackScrollLayout // Only when scene container is enabled, mark that we are being dragged so that we start // dispatching the rest of the gesture to scene container. - void startOverscrollAfterExpanding() { - if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; - getExpandHelper().finishExpanding(); - setIsBeingDragged(true); - } - - // Only when scene container is enabled, mark that we are being dragged so that we start - // dispatching the rest of the gesture to scene container. void startDraggingOnHun() { if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; setIsBeingDragged(true); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 245b1d29fb79..a33a9ed2df75 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -2203,11 +2203,10 @@ public class NotificationStackScrollLayoutController implements Dumpable { expandingNotification = mView.isExpandingNotification(); if (mView.getExpandedInThisMotion() && !expandingNotification && wasExpandingBefore && !mView.getDisallowScrollingInThisMotion()) { - // We need to dispatch the overscroll differently when Scene Container is on, - // since NSSL no longer controls its own scroll. + // Finish expansion here, as this gesture will be marked to be sent to + // scene container if (SceneContainerFlag.isEnabled() && !isCancelOrUp) { - mView.startOverscrollAfterExpanding(); - return true; + expandHelper.finishExpanding(); } else { mView.dispatchDownEventToScroller(ev); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt index 42acd7bcdc8a..705845ff984c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt @@ -75,7 +75,7 @@ class SharedNotificationContainer(context: Context, attrs: AttributeSet?) : constraintSet.apply { if (SceneContainerFlag.isEnabled) { when (horizontalPosition) { - is HorizontalPosition.FloatAtEnd -> + is HorizontalPosition.FloatAtStart -> constrainWidth(nsslId, horizontalPosition.width) is HorizontalPosition.MiddleToEdge -> setGuidelinePercent(R.id.nssl_guideline, horizontalPosition.ratio) @@ -83,13 +83,13 @@ class SharedNotificationContainer(context: Context, attrs: AttributeSet?) : } } + connect(nsslId, START, startConstraintId, START, marginStart) if ( !SceneContainerFlag.isEnabled || - horizontalPosition !is HorizontalPosition.FloatAtEnd + horizontalPosition !is HorizontalPosition.FloatAtStart ) { - connect(nsslId, START, startConstraintId, START, marginStart) + connect(nsslId, END, PARENT_ID, END, marginEnd) } - connect(nsslId, END, PARENT_ID, END, marginEnd) connect(nsslId, BOTTOM, PARENT_ID, BOTTOM, marginBottom) connect(nsslId, TOP, PARENT_ID, TOP, marginTop) } 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 b81c71ebe19b..fc8c70fb8e9a 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 @@ -247,7 +247,7 @@ constructor( Split -> HorizontalPosition.MiddleToEdge(ratio = 0.5f) Dual -> if (isShadeLayoutWide) { - HorizontalPosition.FloatAtEnd( + HorizontalPosition.FloatAtStart( width = getDimensionPixelSize(R.dimen.shade_panel_width) ) } else { @@ -830,10 +830,10 @@ constructor( data class MiddleToEdge(val ratio: Float = 0.5f) : HorizontalPosition /** - * The container has a fixed [width] and is aligned to the end of the screen. In this - * layout, the start edge of the container is floating, i.e. unconstrained. + * The container has a fixed [width] and is aligned to the start of the screen. In this + * layout, the end edge of the container is floating, i.e. unconstrained. */ - data class FloatAtEnd(val width: Int) : HorizontalPosition + data class FloatAtStart(val width: Int) : HorizontalPosition } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 7bea4800f7fc..6dc25aa5144f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -21,9 +21,10 @@ import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.WindowVisibleState; import static android.app.StatusBarManager.windowStateToString; +import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO; +import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO; +import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS; -import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO; -import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS; import static androidx.lifecycle.Lifecycle.State.RESUMED; import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; @@ -207,6 +208,7 @@ import com.android.systemui.statusbar.data.repository.StatusBarModeRepositorySto import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; +import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -221,7 +223,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController.Configurati import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; import com.android.systemui.statusbar.policy.ExtensionController; -import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.window.StatusBarWindowControllerStore; @@ -2514,12 +2515,15 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { * should update only the status bar components. */ private void setBouncerShowingForStatusBarComponents(boolean bouncerShowing) { - int importance = bouncerShowing - ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS - : IMPORTANT_FOR_ACCESSIBILITY_AUTO; if (!StatusBarConnectedDisplays.isEnabled() && mPhoneStatusBarViewController != null) { + int importance = bouncerShowing + ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + : IMPORTANT_FOR_ACCESSIBILITY_AUTO; mPhoneStatusBarViewController.setImportantForAccessibility(importance); } + int importance = bouncerShowing + ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + : IMPORTANT_FOR_ACCESSIBILITY_NO; mShadeSurface.setImportantForAccessibility(importance); mShadeSurface.setBouncerShowing(bouncerShowing); } 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 0fac6448909c..f37bc6b2d4fb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -37,12 +37,10 @@ import androidx.annotation.NonNull; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.InitController; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.res.R; -import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.QuickSettingsController; import com.android.systemui.shade.ShadeViewController; @@ -61,7 +59,6 @@ import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; import com.android.systemui.statusbar.notification.domain.interactor.NotificationAlertsInteractor; -import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor; import com.android.systemui.statusbar.notification.interruption.VisualInterruptionCondition; import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider; @@ -72,6 +69,7 @@ import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; +import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import java.util.Set; @@ -104,7 +102,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu private final IStatusBarService mBarService; private final DynamicPrivacyController mDynamicPrivacyController; private final NotificationListContainer mNotifListContainer; - private final DeviceUnlockedInteractor mDeviceUnlockedInteractor; private final QuickSettingsController mQsController; protected boolean mVrMode; @@ -136,8 +133,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu VisualInterruptionDecisionProvider visualInterruptionDecisionProvider, NotificationRemoteInputManager remoteInputManager, NotificationRemoteInputManager.Callback remoteInputManagerCallback, - NotificationListContainer notificationListContainer, - DeviceUnlockedInteractor deviceUnlockedInteractor) { + NotificationListContainer notificationListContainer) { mActivityStarter = activityStarter; mKeyguardStateController = keyguardStateController; mNotificationPanel = panel; @@ -164,7 +160,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); mNotifListContainer = notificationListContainer; - mDeviceUnlockedInteractor = deviceUnlockedInteractor; IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService( Context.VR_SERVICE)); @@ -253,25 +248,14 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) { mShadeTransitionController.goToLockedShade(clickedEntry.getRow()); } else if (clickedEntry.isSensitive().getValue() - && isInLockedDownShade()) { + && mDynamicPrivacyController.isInLockedDownShade()) { mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); - // launch the bouncer mActivityStarter.dismissKeyguardThenExecute(() -> false /* dismissAction */ , null /* cancelRunnable */, false /* afterKeyguardGone */); } } } - /** @return true if the Shade is shown over the Lockscreen, and the device is locked */ - private boolean isInLockedDownShade() { - if (SceneContainerFlag.isEnabled()) { - return mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED - && !mDeviceUnlockedInteractor.getDeviceUnlockStatus().getValue().isUnlocked(); - } else { - return mDynamicPrivacyController.isInLockedDownShade(); - } - } - @Override public boolean isDeviceInVrMode() { return mVrMode; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java index 78954dea27ba..8b60ee56d5f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -192,7 +192,7 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> mUiEventLogger.log( LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_SWITCH_USER_TAP); - mUserSwitchDialogController.showDialog(mUserAvatarViewWithBackground.getContext(), + mUserSwitchDialogController.showDialog( Expandable.fromView(mUserAvatarViewWithBackground)); }); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt index a1d5cbea62f9..9ff0d18f0e2b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt @@ -44,6 +44,7 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dialog.ui.composable.AlertDialogContent import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R +import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor import com.android.systemui.statusbar.phone.ComponentSystemUIDialog import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.SystemUIDialogFactory @@ -67,6 +68,7 @@ constructor( private val viewModel: Provider<ModesDialogViewModel>, private val dialogEventLogger: ModesDialogEventLogger, @Main private val mainCoroutineContext: CoroutineContext, + private val shadeDisplayContextRepository: ShadeDialogContextInteractor, ) : SystemUIDialog.Delegate { // NOTE: This should only be accessed/written from the main thread. @VisibleForTesting var currentDialog: ComponentSystemUIDialog? = null @@ -78,7 +80,10 @@ constructor( currentDialog?.dismiss() } - currentDialog = sysuiDialogFactory.create { ModesDialogContent(it) } + currentDialog = + sysuiDialogFactory.create(context = shadeDisplayContextRepository.context) { + ModesDialogContent(it) + } currentDialog ?.lifecycle ?.addObserver( @@ -106,9 +111,8 @@ constructor( modifier = Modifier.semantics { testTagsAsResourceId = true - paneTitle = dialog.context.getString( - R.string.accessibility_desc_quick_settings - ) + paneTitle = + dialog.context.getString(R.string.accessibility_desc_quick_settings) }, title = { Text( diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt index a382cf921152..e08114f6c3cd 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt @@ -21,10 +21,8 @@ import android.content.Context import android.hardware.devicestate.DeviceStateManager import android.os.PowerManager import android.provider.Settings -import androidx.core.view.OneShotPreDrawListener import com.android.internal.util.LatencyTracker import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.ToAodFoldTransitionInteractor @@ -125,11 +123,7 @@ constructor( private val shadeFoldAnimator: ShadeFoldAnimator get() { - return if (MigrateClocksToBlueprint.isEnabled) { - foldTransitionInteractor.get().foldAnimator - } else { - shadeViewController.shadeFoldAnimator - } + return foldTransitionInteractor.get().foldAnimator } private fun setAnimationState(playing: Boolean) { @@ -164,15 +158,7 @@ constructor( setAnimationState(playing = true) shadeFoldAnimator.prepareFoldToAodAnimation() - // We don't need to wait for the scrim as it is already displayed - // but we should wait for the initial animation preparations to be drawn - // (setting initial alpha/translation) - // TODO(b/254878364): remove this call to NPVC.getView() - if (!MigrateClocksToBlueprint.isEnabled) { - shadeFoldAnimator.view?.let { OneShotPreDrawListener.add(it, onReady) } - } else { - onReady.run() - } + onReady.run() } else { // No animation, call ready callback immediately onReady.run() @@ -252,7 +238,7 @@ constructor( if (isFolded) { foldToAodLatencyTracker.onFolded() } - } + }, ) /** @@ -272,6 +258,7 @@ constructor( latencyTracker.onActionStart(LatencyTracker.ACTION_FOLD_TO_AOD) } } + /** * Called once the Fold -> AOD animation is started. * diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt index 102fcc0c59f2..e4b2dc25e411 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt @@ -18,7 +18,6 @@ package com.android.systemui.user.ui.dialog import android.app.Dialog -import android.content.Context import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.UiEventLogger import com.android.settingslib.users.UserCreatingDialog @@ -32,6 +31,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.tiles.UserDetailView +import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor import com.android.systemui.user.UserSwitchFullscreenDialog import com.android.systemui.user.domain.interactor.UserSwitcherInteractor import com.android.systemui.user.domain.model.ShowDialogRequestModel @@ -48,7 +48,6 @@ import com.android.app.tracing.coroutines.launchTraced as launch class UserSwitcherDialogCoordinator @Inject constructor( - @Application private val context: Lazy<Context>, @Application private val applicationScope: Lazy<CoroutineScope>, private val falsingManager: Lazy<FalsingManager>, private val broadcastSender: Lazy<BroadcastSender>, @@ -59,6 +58,7 @@ constructor( private val activityStarter: Lazy<ActivityStarter>, private val falsingCollector: Lazy<FalsingCollector>, private val userSwitcherViewModel: Lazy<UserSwitcherViewModel>, + private val shadeDialogContextInteractor: Lazy<ShadeDialogContextInteractor>, ) : CoreStartable { private var currentDialog: Dialog? = null @@ -71,12 +71,13 @@ constructor( private fun startHandlingDialogShowRequests() { applicationScope.get().launch { interactor.get().dialogShowRequests.filterNotNull().collect { request -> + val context = shadeDialogContextInteractor.get().context val (dialog, dialogCuj) = when (request) { is ShowDialogRequestModel.ShowAddUserDialog -> Pair( AddUserDialog( - context = context.get(), + context = context, userHandle = request.userHandle, isKeyguardShowing = request.isKeyguardShowing, showEphemeralMessage = request.showEphemeralMessage, @@ -92,7 +93,7 @@ constructor( is ShowDialogRequestModel.ShowUserCreationDialog -> Pair( UserCreatingDialog( - context.get(), + context, request.isGuest, ), null, @@ -100,7 +101,7 @@ constructor( is ShowDialogRequestModel.ShowExitGuestDialog -> Pair( ExitGuestDialog( - context = context.get(), + context = context, guestUserId = request.guestUserId, isGuestEphemeral = request.isGuestEphemeral, targetUserId = request.targetUserId, @@ -117,7 +118,7 @@ constructor( is ShowDialogRequestModel.ShowUserSwitcherDialog -> Pair( UserSwitchDialog( - context = context.get(), + context = context, adapter = userDetailAdapterProvider.get(), uiEventLogger = eventLogger.get(), falsingManager = falsingManager.get(), @@ -132,7 +133,7 @@ constructor( is ShowDialogRequestModel.ShowUserSwitcherFullscreenDialog -> Pair( UserSwitchFullscreenDialog( - context = context.get(), + context = context, falsingCollector = falsingCollector.get(), userSwitcherViewModel = userSwitcherViewModel.get(), ), diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt index 2e1f82d56fc4..70e342f3eefb 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt @@ -17,45 +17,28 @@ package com.android.systemui.volume.dialog.settings.ui.binder import android.view.View -import com.android.systemui.lifecycle.WindowLifecycleState -import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.lifecycle.setSnapshotBinding -import com.android.systemui.lifecycle.viewModel +import android.widget.ImageButton import com.android.systemui.res.R import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope import com.android.systemui.volume.dialog.settings.ui.viewmodel.VolumeDialogSettingsButtonViewModel import javax.inject.Inject -import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @VolumeDialogScope class VolumeDialogSettingsButtonViewBinder @Inject -constructor(private val viewModelFactory: VolumeDialogSettingsButtonViewModel.Factory) { +constructor(private val viewModel: VolumeDialogSettingsButtonViewModel) { - fun bind(view: View) { - with(view) { - val button = requireViewById<View>(R.id.volume_dialog_settings) - repeatWhenAttached { - viewModel( - traceName = "VolumeDialogViewBinder", - minWindowLifecycleState = WindowLifecycleState.ATTACHED, - factory = { viewModelFactory.create() }, - ) { viewModel -> - setSnapshotBinding { - viewModel.isVisible - .onEach { isVisible -> - visibility = if (isVisible) View.VISIBLE else View.GONE - } - .launchIn(this) + fun CoroutineScope.bind(view: View) { + val button = view.requireViewById<ImageButton>(R.id.volume_dialog_settings) + viewModel.isVisible + .onEach { isVisible -> button.visibility = if (isVisible) View.VISIBLE else View.GONE } + .launchIn(this) - button.setOnClickListener { viewModel.onButtonClicked() } - } + viewModel.icon.onEach { button.setImageDrawable(it) }.launchIn(this) - awaitCancellation() - } - } - } + button.setOnClickListener { viewModel.onButtonClicked() } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModel.kt index 015d773b2c02..03442dbcde66 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModel.kt @@ -14,27 +14,206 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.volume.dialog.settings.ui.viewmodel -import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.ColorFilter +import android.graphics.drawable.Drawable +import android.media.session.PlaybackState +import androidx.annotation.ColorInt +import com.airbnb.lottie.LottieComposition +import com.airbnb.lottie.LottieCompositionFactory +import com.airbnb.lottie.LottieDrawable +import com.airbnb.lottie.LottieProperty +import com.airbnb.lottie.SimpleColorFilter +import com.airbnb.lottie.model.KeyPath +import com.airbnb.lottie.value.LottieValueCallback +import com.android.internal.R as internalR +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.UiBackground +import com.android.systemui.lottie.await +import com.android.systemui.res.R +import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog import com.android.systemui.volume.dialog.settings.domain.VolumeDialogSettingsButtonInteractor -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject +import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor +import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor +import com.android.systemui.volume.panel.shared.model.filterData +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.resume +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.buffer +import kotlinx.coroutines.flow.distinctUntilChangedBy +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.runningFold +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.transform +import kotlinx.coroutines.suspendCancellableCoroutine class VolumeDialogSettingsButtonViewModel -@AssistedInject -constructor(private val interactor: VolumeDialogSettingsButtonInteractor) { +@Inject +constructor( + @Application private val context: Context, + @UiBackground private val uiBgCoroutineContext: CoroutineContext, + @VolumeDialog private val coroutineScope: CoroutineScope, + mediaOutputInteractor: MediaOutputInteractor, + private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor, + private val interactor: VolumeDialogSettingsButtonInteractor, +) { + + @SuppressLint("UseCompatLoadingForDrawables") + private val drawables: Flow<Drawables> = + flow { + val color = context.getColor(internalR.color.materialColorPrimary) + emit( + Drawables( + start = + LottieCompositionFactory.fromRawRes(context, R.raw.audio_bars_in) + .await() + .toDrawable { setColor(color) }, + playing = + LottieCompositionFactory.fromRawRes(context, R.raw.audio_bars_playing) + .await() + .toDrawable { + repeatCount = LottieDrawable.INFINITE + repeatMode = LottieDrawable.RESTART + setColor(color) + }, + stop = + LottieCompositionFactory.fromRawRes(context, R.raw.audio_bars_out) + .await() + .toDrawable { setColor(color) }, + idle = context.getDrawable(R.drawable.audio_bars_idle)!!, + ) + ) + } + .buffer() + .flowOn(uiBgCoroutineContext) + .stateIn(coroutineScope, SharingStarted.Eagerly, null) + .filterNotNull() val isVisible = interactor.isVisible + val icon: Flow<Drawable> = + mediaOutputInteractor.defaultActiveMediaSession + .filterData() + .flatMapLatest { session -> + if (session == null) { + flowOf(null) + } else { + mediaDeviceSessionInteractor.playbackState(session) + } + } + .runningFold(null) { playbackStates: PlaybackStates?, playbackState: PlaybackState? -> + val isCurrentActive = playbackState?.isActive ?: false + if (playbackStates != null && isCurrentActive == playbackState?.isActive) { + return@runningFold playbackStates + } + playbackStates?.copy( + isPreviousActive = playbackStates.isCurrentActive, + isCurrentActive = isCurrentActive, + ) ?: PlaybackStates(isPreviousActive = null, isCurrentActive = isCurrentActive) + } + .filterNotNull() + // only apply the most recent state if we wait for the animation. + .buffer(capacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) + // distinct again because the changed state might've been dropped by the buffer + .distinctUntilChangedBy { it.isCurrentActive } + .transform { emitDrawables(it) } + .runningFold(null) { previous: Drawable?, current: Drawable -> + // wait for the previous animation to finish before starting the new one + // this also waits for the current loop of the playing animation to finish + (previous as? LottieDrawable)?.awaitFinish() + (current as? LottieDrawable)?.start() + current + } + .filterNotNull() + + private suspend fun FlowCollector<Drawable>.emitDrawables(playbackStates: PlaybackStates) { + val animations = drawables.first() + val stateChanged = + playbackStates.isPreviousActive != null && + playbackStates.isPreviousActive != playbackStates.isCurrentActive + if (playbackStates.isCurrentActive) { + if (stateChanged) { + emit(animations.start) + } + emit(animations.playing) + } else { + if (stateChanged) { + emit(animations.stop) + } + emit(animations.idle) + } + } fun onButtonClicked() { interactor.onButtonClicked() } - @VolumeDialogScope - @AssistedFactory - interface Factory { + private data class PlaybackStates(val isPreviousActive: Boolean?, val isCurrentActive: Boolean) + + private data class Drawables( + val start: LottieDrawable, + val playing: LottieDrawable, + val stop: LottieDrawable, + val idle: Drawable, + ) +} + +private fun LottieComposition.toDrawable(setup: LottieDrawable.() -> Unit = {}): LottieDrawable = + LottieDrawable().also { drawable -> + drawable.composition = this + drawable.setup() + } - fun create(): VolumeDialogSettingsButtonViewModel +/** Suspends until current loop of the repeating animation is finished */ +private suspend fun LottieDrawable.awaitFinish() = suspendCancellableCoroutine { continuation -> + if (!isRunning) { + continuation.resume(Unit) + return@suspendCancellableCoroutine } + val listener = + object : AnimatorListenerAdapter() { + override fun onAnimationRepeat(animation: Animator) { + continuation.resume(Unit) + removeAnimatorListener(this) + } + + override fun onAnimationEnd(animation: Animator) { + continuation.resume(Unit) + removeAnimatorListener(this) + } + + override fun onAnimationCancel(animation: Animator) { + continuation.resume(Unit) + removeAnimatorListener(this) + } + } + addAnimatorListener(listener) + continuation.invokeOnCancellation { removeAnimatorListener(listener) } +} + +/** + * Overrides colors of the [LottieDrawable] to a specified [color] + * + * @see com.airbnb.lottie.LottieAnimationView + */ +private fun LottieDrawable.setColor(@ColorInt color: Int) { + val callback = LottieValueCallback<ColorFilter>(SimpleColorFilter(color)) + addValueCallback(KeyPath("**"), LottieProperty.COLOR_FILTER, callback) } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt index f30524638150..faf06b942cab 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt @@ -26,7 +26,7 @@ import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderStateModel import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory -import com.android.systemui.volume.dialog.ui.utils.awaitAnimation +import com.android.systemui.volume.dialog.ui.utils.suspendAnimate import com.google.android.material.slider.LabelFormatter import com.google.android.material.slider.Slider import javax.inject.Inject @@ -84,5 +84,5 @@ private suspend fun Slider.setValueAnimated( interpolator = DecelerateInterpolator() addListener(jankListener) } - .awaitAnimation<Float> { value = it } + .suspendAnimate<Float> { value = it } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt index 10cf615ce0ce..5f124806dac7 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt @@ -22,6 +22,7 @@ import android.animation.ValueAnimator import android.view.ViewPropertyAnimator import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.SpringAnimation +import com.airbnb.lottie.LottieDrawable import kotlin.coroutines.resume import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.suspendCancellableCoroutine @@ -66,7 +67,7 @@ suspend fun ViewPropertyAnimator.suspendAnimate( * is cancelled. */ @Suppress("UNCHECKED_CAST") -suspend fun <T> ValueAnimator.awaitAnimation(onValueChanged: (T) -> Unit) { +suspend fun <T> ValueAnimator.suspendAnimate(onValueChanged: (T) -> Unit) { suspendCancellableCoroutine { continuation -> addListener( object : AnimatorListenerAdapter() { @@ -103,6 +104,29 @@ suspend fun SpringAnimation.suspendAnimate( } } +/** + * Starts the animation and suspends until it's finished. Cancels the animation if the running + * coroutine is cancelled. + */ +suspend fun LottieDrawable.suspendAnimate() = suspendCancellableCoroutine { continuation -> + val listener = + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + continuation.resumeIfCan(Unit) + } + + override fun onAnimationCancel(animation: Animator) { + continuation.resumeIfCan(Unit) + } + } + addAnimatorListener(listener) + start() + continuation.invokeOnCancellation { + removeAnimatorListener(listener) + stop() + } +} + private fun <T> CancellableContinuation<T>.resumeIfCan(value: T) { if (!isCancelled && !isCompleted) { resume(value) diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt index 6e1ebc820b08..12e624cae4d4 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt @@ -19,10 +19,10 @@ package com.android.systemui.volume.panel.component.mediaoutput.domain.interacto import android.media.session.MediaController import android.media.session.PlaybackState import com.android.settingslib.volume.data.repository.MediaControllerRepository +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession -import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope import javax.inject.Inject import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -38,7 +38,7 @@ import kotlinx.coroutines.withContext /** Allows to observe and change [MediaDeviceSession] state. */ @OptIn(ExperimentalCoroutinesApi::class) -@VolumePanelScope +@SysUISingleton class MediaDeviceSessionInteractor @Inject constructor( diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt index b3848a6d7817..2973e11c365d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt @@ -24,12 +24,13 @@ import androidx.annotation.WorkerThread import com.android.settingslib.media.MediaDevice import com.android.settingslib.volume.data.repository.LocalMediaRepository import com.android.settingslib.volume.data.repository.MediaControllerRepository +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.util.concurrency.Execution import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSessions import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession -import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope import com.android.systemui.volume.panel.shared.model.Result import com.android.systemui.volume.panel.shared.model.filterData import com.android.systemui.volume.panel.shared.model.wrapInResult @@ -54,13 +55,13 @@ import kotlinx.coroutines.withContext /** Provides observable models about the current media session state. */ @OptIn(ExperimentalCoroutinesApi::class) -@VolumePanelScope +@SysUISingleton class MediaOutputInteractor @Inject constructor( private val localMediaRepositoryFactory: LocalMediaRepositoryFactory, private val packageManager: PackageManager, - @VolumePanelScope private val coroutineScope: CoroutineScope, + @Application private val coroutineScope: CoroutineScope, @Background private val backgroundCoroutineContext: CoroutineContext, mediaControllerRepository: MediaControllerRepository, private val mediaControllerInteractor: MediaControllerInteractor, @@ -77,7 +78,7 @@ constructor( .onStart { emit(activeSessions) } } .map { getMediaControllers(it) } - .stateIn(coroutineScope, SharingStarted.Eagerly, MediaControllers(null, null)) + .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), MediaControllers(null, null)) /** [MediaDeviceSessions] that contains currently active sessions. */ val activeMediaDeviceSessions: Flow<MediaDeviceSessions> = @@ -89,7 +90,11 @@ constructor( ) } .flowOn(backgroundCoroutineContext) - .stateIn(coroutineScope, SharingStarted.Eagerly, MediaDeviceSessions(null, null)) + .stateIn( + coroutineScope, + SharingStarted.WhileSubscribed(), + MediaDeviceSessions(null, null), + ) /** Returns the default [MediaDeviceSession] from [activeMediaDeviceSessions] */ val defaultActiveMediaSession: StateFlow<Result<MediaDeviceSession?>> = @@ -104,7 +109,7 @@ constructor( } .wrapInResult() .flowOn(backgroundCoroutineContext) - .stateIn(coroutineScope, SharingStarted.Eagerly, Result.Loading()) + .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), Result.Loading()) private val localMediaRepository: Flow<LocalMediaRepository> = defaultActiveMediaSession diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index a41725f754df..4abbbacd800b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -25,7 +25,6 @@ import android.widget.FrameLayout import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND_INACTIVE -import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.flags.Flags @@ -299,20 +298,6 @@ class ClockEventControllerTest : SysuiTestCase() { } @Test - @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun keyguardCallback_visibilityChanged_clockDozeCalled() = - runBlocking(IMMEDIATE) { - val captor = argumentCaptor<KeyguardUpdateMonitorCallback>() - verify(keyguardUpdateMonitor).registerCallback(capture(captor)) - - captor.value.onKeyguardVisibilityChanged(true) - verify(animations, never()).doze(0f) - - captor.value.onKeyguardVisibilityChanged(false) - verify(animations, times(2)).doze(0f) - } - - @Test fun keyguardCallback_timeFormat_clockNotified() = runBlocking(IMMEDIATE) { val captor = argumentCaptor<KeyguardUpdateMonitorCallback>() @@ -344,19 +329,6 @@ class ClockEventControllerTest : SysuiTestCase() { } @Test - fun keyguardCallback_verifyKeyguardChanged() = - runBlocking(IMMEDIATE) { - val job = underTest.listenForDozeAmount(this) - repository.setDozeAmount(0.4f) - - yield() - - verify(animations, times(2)).doze(0.4f) - - job.cancel() - } - - @Test fun listenForDozeAmountTransition_updatesClockDozeAmount() = runBlocking(IMMEDIATE) { val transitionStep = MutableStateFlow(TransitionStep()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt index 4aaa82e4a16d..37eb148a5ea7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt @@ -14,6 +14,8 @@ import android.testing.TestableLooper.RunWithLooper import android.view.IRemoteAnimationFinishedCallback import android.view.RemoteAnimationAdapter import android.view.RemoteAnimationTarget +import android.view.RemoteAnimationTarget.MODE_CLOSING +import android.view.RemoteAnimationTarget.MODE_OPENING import android.view.SurfaceControl import android.view.ViewGroup import android.view.WindowManager.TRANSIT_NONE @@ -36,10 +38,6 @@ import junit.framework.Assert.assertTrue import junit.framework.AssertionFailedError import kotlin.concurrent.thread import kotlin.test.assertEquals -import kotlin.time.Duration.Companion.seconds -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withTimeout import org.junit.After import org.junit.Assert.assertThrows import org.junit.Before @@ -49,6 +47,7 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.Mock +import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.`when` @@ -215,22 +214,12 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { ) @Test fun registersLongLivedTransition() { - activityTransitionAnimator.register( - object : DelegateTransitionAnimatorController(controller) { - override val transitionCookie = - ActivityTransitionAnimator.TransitionCookie("test_cookie_1") - override val component = ComponentName("com.test.package", "Test1") - } - ) + var factory = controllerFactory() + activityTransitionAnimator.register(factory.cookie, factory) assertEquals(2, testShellTransitions.remotes.size) - activityTransitionAnimator.register( - object : DelegateTransitionAnimatorController(controller) { - override val transitionCookie = - ActivityTransitionAnimator.TransitionCookie("test_cookie_2") - override val component = ComponentName("com.test.package", "Test2") - } - ) + factory = controllerFactory() + activityTransitionAnimator.register(factory.cookie, factory) assertEquals(4, testShellTransitions.remotes.size) } @@ -241,20 +230,12 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { @Test fun registersLongLivedTransitionOverridingPreviousRegistration() { val cookie = ActivityTransitionAnimator.TransitionCookie("test_cookie") - activityTransitionAnimator.register( - object : DelegateTransitionAnimatorController(controller) { - override val transitionCookie = cookie - override val component = ComponentName("com.test.package", "Test1") - } - ) + var factory = controllerFactory(cookie) + activityTransitionAnimator.register(cookie, factory) val transitions = testShellTransitions.remotes.values.toList() - activityTransitionAnimator.register( - object : DelegateTransitionAnimatorController(controller) { - override val transitionCookie = cookie - override val component = ComponentName("com.test.package", "Test2") - } - ) + factory = controllerFactory(cookie) + activityTransitionAnimator.register(cookie, factory) assertEquals(2, testShellTransitions.remotes.size) for (transition in transitions) { assertThat(testShellTransitions.remotes.values).doesNotContain(transition) @@ -264,38 +245,19 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { @DisableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED) @Test fun doesNotRegisterLongLivedTransitionIfFlagIsDisabled() { - val controller = - object : DelegateTransitionAnimatorController(controller) { - override val transitionCookie = - ActivityTransitionAnimator.TransitionCookie("test_cookie") - override val component = ComponentName("com.test.package", "Test") - } + val factory = controllerFactory(component = null) assertThrows(IllegalStateException::class.java) { - activityTransitionAnimator.register(controller) + activityTransitionAnimator.register(factory.cookie, factory) } } @EnableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED) @Test fun doesNotRegisterLongLivedTransitionIfMissingRequiredProperties() { - // No TransitionCookie - val controllerWithoutCookie = - object : DelegateTransitionAnimatorController(controller) { - override val transitionCookie = null - } - assertThrows(IllegalStateException::class.java) { - activityTransitionAnimator.register(controllerWithoutCookie) - } - // No ComponentName - val controllerWithoutComponent = - object : DelegateTransitionAnimatorController(controller) { - override val transitionCookie = - ActivityTransitionAnimator.TransitionCookie("test_cookie") - override val component = null - } + var factory = controllerFactory(component = null) assertThrows(IllegalStateException::class.java) { - activityTransitionAnimator.register(controllerWithoutComponent) + activityTransitionAnimator.register(factory.cookie, factory) } // No TransitionRegister @@ -307,14 +269,9 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { testTransitionAnimator, disableWmTimeout = true, ) - val validController = - object : DelegateTransitionAnimatorController(controller) { - override val transitionCookie = - ActivityTransitionAnimator.TransitionCookie("test_cookie") - override val component = ComponentName("com.test.package", "Test") - } + factory = controllerFactory() assertThrows(IllegalStateException::class.java) { - activityTransitionAnimator.register(validController) + activityTransitionAnimator.register(factory.cookie, factory) } } @@ -324,18 +281,12 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { ) @Test fun unregistersLongLivedTransition() { - val cookies = arrayOfNulls<ActivityTransitionAnimator.TransitionCookie>(3) for (index in 0 until 3) { - cookies[index] = ActivityTransitionAnimator.TransitionCookie("test_cookie_$index") - - val controller = - object : DelegateTransitionAnimatorController(controller) { - override val transitionCookie = cookies[index] - override val component = ComponentName("foo.bar", "Foobar") - } - activityTransitionAnimator.register(controller) + cookies[index] = mock(ActivityTransitionAnimator.TransitionCookie::class.java) + val factory = controllerFactory(cookies[index]!!) + activityTransitionAnimator.register(factory.cookie, factory) } activityTransitionAnimator.unregister(cookies[0]!!) @@ -350,7 +301,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { @Test fun doesNotStartIfAnimationIsCancelled() { - val runner = activityTransitionAnimator.createRunner(controller) + val runner = activityTransitionAnimator.createEphemeralRunner(controller) runner.onAnimationCancelled() runner.onAnimationStart(TRANSIT_NONE, emptyArray(), emptyArray(), emptyArray(), iCallback) @@ -364,7 +315,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { @Test fun cancelsIfNoOpeningWindowIsFound() { - val runner = activityTransitionAnimator.createRunner(controller) + val runner = activityTransitionAnimator.createEphemeralRunner(controller) runner.onAnimationStart(TRANSIT_NONE, emptyArray(), emptyArray(), emptyArray(), iCallback) waitForIdleSync() @@ -377,7 +328,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { @Test fun startsAnimationIfWindowIsOpening() { - val runner = activityTransitionAnimator.createRunner(controller) + val runner = activityTransitionAnimator.createEphemeralRunner(controller) runner.onAnimationStart( TRANSIT_NONE, arrayOf(fakeWindow()), @@ -404,7 +355,8 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { @Test fun creatingRunnerWithLazyInitializationThrows_whenTheFlagsAreDisabled() { assertThrows(IllegalStateException::class.java) { - activityTransitionAnimator.createRunner(controller, longLived = true) + val factory = controllerFactory() + activityTransitionAnimator.createLongLivedRunner(factory, forLaunch = true) } } @@ -414,7 +366,8 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { ) @Test fun runnerCreatesDelegateLazily_whenPostingTimeouts() { - val runner = activityTransitionAnimator.createRunner(controller, longLived = true) + val factory = controllerFactory() + val runner = activityTransitionAnimator.createLongLivedRunner(factory, forLaunch = true) assertNull(runner.delegate) runner.postTimeouts() assertNotNull(runner.delegate) @@ -426,29 +379,29 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { ) @Test fun runnerCreatesDelegateLazily_onAnimationStart() { - val runner = activityTransitionAnimator.createRunner(controller, longLived = true) + val factory = controllerFactory() + val runner = activityTransitionAnimator.createLongLivedRunner(factory, forLaunch = true) assertNull(runner.delegate) - // The delegate is cleaned up after execution (which happens in another thread), so what we - // do instead is check if it becomes non-null at any point with a 1 second timeout. This - // will tell us that takeOverWithAnimation() triggered the lazy initialization. var delegateInitialized = false - runBlocking { - val initChecker = launch { - withTimeout(1.seconds) { - while (runner.delegate == null) continue + activityTransitionAnimator.addListener( + object : ActivityTransitionAnimator.Listener { + override fun onTransitionAnimationStart() { + // This is called iff the delegate was initialized, so it's a good proxy for + // checking the initialization. delegateInitialized = true } } - runner.onAnimationStart( - TRANSIT_NONE, - arrayOf(fakeWindow()), - emptyArray(), - emptyArray(), - iCallback, - ) - initChecker.join() - } + ) + runner.onAnimationStart( + TRANSIT_NONE, + arrayOf(fakeWindow()), + emptyArray(), + emptyArray(), + iCallback, + ) + + waitForIdleSync() assertTrue(delegateInitialized) } @@ -458,28 +411,28 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { ) @Test fun runnerCreatesDelegateLazily_onAnimationTakeover() { - val runner = activityTransitionAnimator.createRunner(controller, longLived = true) + val factory = controllerFactory() + val runner = activityTransitionAnimator.createLongLivedRunner(factory, forLaunch = false) assertNull(runner.delegate) - // The delegate is cleaned up after execution (which happens in another thread), so what we - // do instead is check if it becomes non-null at any point with a 1 second timeout. This - // will tell us that takeOverWithAnimation() triggered the lazy initialization. var delegateInitialized = false - runBlocking { - val initChecker = launch { - withTimeout(1.seconds) { - while (runner.delegate == null) continue + activityTransitionAnimator.addListener( + object : ActivityTransitionAnimator.Listener { + override fun onTransitionAnimationStart() { + // This is called iff the delegate was initialized, so it's a good proxy for + // checking the initialization. delegateInitialized = true } } - runner.takeOverAnimation( - arrayOf(fakeWindow()), - arrayOf(WindowAnimationState()), - SurfaceControl.Transaction(), - iCallback, - ) - initChecker.join() - } + ) + runner.takeOverAnimation( + arrayOf(fakeWindow(MODE_CLOSING)), + arrayOf(WindowAnimationState()), + SurfaceControl.Transaction(), + iCallback, + ) + + waitForIdleSync() assertTrue(delegateInitialized) } @@ -489,7 +442,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { ) @Test fun animationTakeoverThrows_whenTheFlagsAreDisabled() { - val runner = activityTransitionAnimator.createRunner(controller, longLived = false) + val runner = activityTransitionAnimator.createEphemeralRunner(controller) assertThrows(IllegalStateException::class.java) { runner.takeOverAnimation( arrayOf(fakeWindow()), @@ -506,14 +459,28 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { ) @Test fun disposeRunner_delegateDereferenced() { - val runner = activityTransitionAnimator.createRunner(controller) + val runner = activityTransitionAnimator.createEphemeralRunner(controller) assertNotNull(runner.delegate) runner.dispose() waitForIdleSync() assertNull(runner.delegate) } - private fun fakeWindow(): RemoteAnimationTarget { + private fun controllerFactory( + cookie: ActivityTransitionAnimator.TransitionCookie = + mock(ActivityTransitionAnimator.TransitionCookie::class.java), + component: ComponentName? = mock(ComponentName::class.java), + ): ActivityTransitionAnimator.ControllerFactory { + return object : ActivityTransitionAnimator.ControllerFactory(cookie, component) { + override fun createController(forLaunch: Boolean) = + object : DelegateTransitionAnimatorController(controller) { + override val isLaunching: Boolean + get() = forLaunch + } + } + } + + private fun fakeWindow(mode: Int = MODE_OPENING): RemoteAnimationTarget { val bounds = Rect(10 /* left */, 20 /* top */, 30 /* right */, 40 /* bottom */) val taskInfo = ActivityManager.RunningTaskInfo() taskInfo.topActivity = ComponentName("com.android.systemui", "FakeActivity") @@ -521,7 +488,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { return RemoteAnimationTarget( 0, - RemoteAnimationTarget.MODE_OPENING, + mode, SurfaceControl(), false, Rect(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt index fb0fd23fab24..6bfd08025833 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt @@ -34,8 +34,10 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.model.SysUiState import com.android.systemui.res.R +import com.android.systemui.shade.data.repository.shadeDialogContextInteractor import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.SystemUIDialogManager +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock @@ -82,7 +84,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { private val uiProperties = BluetoothTileDialogViewModel.UiProperties.build( isBluetoothEnabled = ENABLED, - isAutoOnToggleFeatureAvailable = ENABLED + isAutoOnToggleFeatureAvailable = ENABLED, ) @Mock private lateinit var sysuiDialogFactory: SystemUIDialog.Factory @Mock private lateinit var dialogManager: SystemUIDialogManager @@ -98,6 +100,8 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { private lateinit var mBluetoothTileDialogDelegate: BluetoothTileDialogDelegate private lateinit var deviceItem: DeviceItem + private val kosmos = testKosmos() + @Before fun setUp() { scheduler = TestCoroutineScheduler() @@ -116,10 +120,16 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { fakeSystemClock, uiEventLogger, logger, - sysuiDialogFactory + sysuiDialogFactory, + kosmos.shadeDialogContextInteractor, ) - whenever(sysuiDialogFactory.create(any(SystemUIDialog.Delegate::class.java))).thenAnswer { + whenever( + sysuiDialogFactory.create( + any(SystemUIDialog.Delegate::class.java), + any() + ) + ).thenAnswer { SystemUIDialog( mContext, 0, @@ -128,7 +138,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { sysuiState, fakeBroadcastDispatcher, dialogTransitionAnimator, - it.getArgument(0) + it.getArgument(0), ) } @@ -140,7 +150,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { deviceName = DEVICE_NAME, connectionSummary = DEVICE_CONNECTION_SUMMARY, iconWithDescription = icon, - background = null + background = null, ) `when`(cachedBluetoothDevice.isBusy).thenReturn(false) } @@ -169,7 +179,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { dialog, listOf(deviceItem), showSeeAll = false, - showPairNewDevice = false + showPairNewDevice = false, ) val recyclerView = dialog.requireViewById<RecyclerView>(R.id.device_list) @@ -217,6 +227,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { uiEventLogger, logger, sysuiDialogFactory, + kosmos.shadeDialogContextInteractor, ) .Adapter(bluetoothTileDialogCallback) .DeviceItemViewHolder(view) @@ -238,7 +249,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { dialog, listOf(deviceItem), showSeeAll = false, - showPairNewDevice = true + showPairNewDevice = true, ) val seeAllButton = dialog.requireViewById<View>(R.id.see_all_button) @@ -272,6 +283,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { uiEventLogger, logger, sysuiDialogFactory, + kosmos.shadeDialogContextInteractor, ) .createDialog() dialog.show() @@ -295,6 +307,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { uiEventLogger, logger, sysuiDialogFactory, + kosmos.shadeDialogContextInteractor, ) .createDialog() dialog.show() @@ -318,6 +331,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { uiEventLogger, logger, sysuiDialogFactory, + kosmos.shadeDialogContextInteractor, ) .createDialog() dialog.show() @@ -339,7 +353,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { dialog, visibility = VISIBLE, label = null, - isActive = true + isActive = true, ) val audioSharingButton = dialog.requireViewById<View>(R.id.audio_sharing_button) @@ -361,7 +375,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { dialog, visibility = VISIBLE, label = null, - isActive = false + isActive = false, ) val audioSharingButton = dialog.requireViewById<View>(R.id.audio_sharing_button) @@ -383,7 +397,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { dialog, visibility = GONE, label = null, - isActive = false + isActive = false, ) val audioSharingButton = dialog.requireViewById<View>(R.id.audio_sharing_button) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java index c2c94a88603a..1cabf202463e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java @@ -77,6 +77,8 @@ public class CommunalTouchHandlerTest extends SysuiTestCase { INITIATION_WIDTH, mKosmos.getCommunalInteractor(), mKosmos.getConfigurationInteractor(), + mKosmos.getSceneInteractor(), + Optional.of(mKosmos.getMockWindowRootViewProvider()), mLifecycle ); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt index 32fa160fc29f..21dd5bc068f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt @@ -344,7 +344,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { canShowWhileLocked = canShowWhileLocked, ) } else { - KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false) } underTest.onQuickAffordanceTriggered( diff --git a/packages/SystemUI/tests/src/com/android/systemui/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt index 1184a76d54ff..1ce128c2403a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt @@ -81,7 +81,7 @@ import platform.test.runner.parameterized.Parameters @OptIn(ExperimentalCoroutinesApi::class) @FlakyTest( bugId = 292574995, - detail = "on certain architectures all permutations with startActivity=true is causing failures" + detail = "on certain architectures all permutations with startActivity=true is causing failures", ) @SmallTest @RunWith(ParameterizedAndroidJunit4::class) @@ -93,11 +93,7 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() { private val DRAWABLE = mock<Icon> { whenever(this.contentDescription) - .thenReturn( - ContentDescription.Resource( - res = CONTENT_DESCRIPTION_RESOURCE_ID, - ) - ) + .thenReturn(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID)) } private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337 @@ -273,13 +269,7 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() { context = context, userFileManager = mock<UserFileManager>().apply { - whenever( - getSharedPreferences( - anyString(), - anyInt(), - anyInt(), - ) - ) + whenever(getSharedPreferences(anyString(), anyInt(), anyInt())) .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, @@ -316,9 +306,7 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() { underTest = KeyguardQuickAffordanceInteractor( keyguardInteractor = - KeyguardInteractorFactory.create( - featureFlags = featureFlags, - ) + KeyguardInteractorFactory.create(featureFlags = featureFlags) .keyguardInteractor, shadeInteractor = kosmos.shadeInteractor, lockPatternUtils = lockPatternUtils, @@ -350,9 +338,7 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() { homeControls.setState( lockScreenState = - KeyguardQuickAffordanceConfig.LockScreenState.Visible( - icon = DRAWABLE, - ) + KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = DRAWABLE) ) homeControls.onTriggeredResult = if (startActivity) { @@ -361,7 +347,7 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() { canShowWhileLocked = canShowWhileLocked, ) } else { - KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false) } underTest.onQuickAffordanceTriggered( diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java index b26f0a6e71a3..782b24825bcf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java @@ -557,6 +557,13 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase { } @Test + public void startActivityForDialog_always_startActivityWithoutDismissShade() { + mInternetDialogController.startActivityForDialog(mock(Intent.class)); + + verify(mActivityStarter).startActivity(any(Intent.class), eq(false) /* dismissShade */); + } + + @Test public void launchWifiDetailsSetting_withNoWifiEntryKey_doNothing() { mInternetDialogController.launchWifiDetailsSetting(null /* key */, mDialogLaunchView); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java index 300c9b843d1c..8560b67dee33 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java @@ -35,6 +35,7 @@ import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.res.R; +import com.android.systemui.shade.domain.interactor.FakeShadeDialogContextInteractor; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecutor; @@ -149,7 +150,8 @@ public class InternetDialogDelegateTest extends SysuiTestCase { mHandler, mBgExecutor, mKeyguard, - mSystemUIDialogFactory); + mSystemUIDialogFactory, + new FakeShadeDialogContextInteractor(mContext)); mInternetDialogDelegate.createDialog(); mInternetDialogDelegate.onCreate(mSystemUIDialog, null); mInternetDialogDelegate.mAdapter = mInternetAdapter; diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt index a0ecb802dd61..f695c13a9e62 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt @@ -76,6 +76,8 @@ class UserTrackerImplTest : SysuiTestCase() { @Mock private lateinit var iActivityManager: IActivityManager + @Mock private lateinit var beforeUserSwitchingReply: IRemoteCallback + @Mock private lateinit var userSwitchingReply: IRemoteCallback @Mock(stubOnly = true) private lateinit var dumpManager: DumpManager @@ -199,9 +201,10 @@ class UserTrackerImplTest : SysuiTestCase() { val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java) verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString()) - captor.value.onBeforeUserSwitching(newID) + captor.value.onBeforeUserSwitching(newID, beforeUserSwitchingReply) captor.value.onUserSwitching(newID, userSwitchingReply) runCurrent() + verify(beforeUserSwitchingReply).sendResult(any()) verify(userSwitchingReply).sendResult(any()) verify(userManager).getProfiles(newID) @@ -341,10 +344,11 @@ class UserTrackerImplTest : SysuiTestCase() { val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java) verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString()) - captor.value.onBeforeUserSwitching(newID) + captor.value.onBeforeUserSwitching(newID, beforeUserSwitchingReply) captor.value.onUserSwitching(newID, userSwitchingReply) runCurrent() + verify(beforeUserSwitchingReply).sendResult(any()) verify(userSwitchingReply).sendResult(any()) assertThat(callback.calledOnUserChanging).isEqualTo(1) assertThat(callback.lastUser).isEqualTo(newID) @@ -395,7 +399,7 @@ class UserTrackerImplTest : SysuiTestCase() { val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java) verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString()) - captor.value.onBeforeUserSwitching(newID) + captor.value.onBeforeUserSwitching(newID, any()) captor.value.onUserSwitchComplete(newID) runCurrent() @@ -453,8 +457,10 @@ class UserTrackerImplTest : SysuiTestCase() { val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java) verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString()) + captor.value.onBeforeUserSwitching(newID, beforeUserSwitchingReply) captor.value.onUserSwitching(newID, userSwitchingReply) runCurrent() + verify(beforeUserSwitchingReply).sendResult(any()) verify(userSwitchingReply).sendResult(any()) captor.value.onUserSwitchComplete(newID) @@ -488,6 +494,7 @@ class UserTrackerImplTest : SysuiTestCase() { } private class TestCallback : UserTracker.Callback { + var calledOnBeforeUserChanging = 0 var calledOnUserChanging = 0 var calledOnUserChanged = 0 var calledOnProfilesChanged = 0 @@ -495,6 +502,11 @@ class UserTrackerImplTest : SysuiTestCase() { var lastUserContext: Context? = null var lastUserProfiles = emptyList<UserInfo>() + override fun onBeforeUserSwitching(newUser: Int) { + calledOnBeforeUserChanging++ + lastUser = newUser + } + override fun onUserChanging(newUser: Int, userContext: Context) { calledOnUserChanging++ lastUser = newUser 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 041d1a611b55..4b11e2c24722 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -17,7 +17,6 @@ package com.android.systemui.shade import android.content.Context -import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.annotations.RequiresFlagsDisabled import android.platform.test.flag.junit.FlagsParameterization @@ -32,7 +31,6 @@ import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardSecurityContainerController import com.android.keyguard.dagger.KeyguardBouncerComponent import com.android.systemui.Flags -import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor @@ -406,18 +404,6 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : } @Test - @DisableSceneContainer - @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun handleDispatchTouchEvent_nsslMigrationOff_userActivity_not_called() { - underTest.setStatusBarViewController(phoneStatusBarViewController) - - interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) - - verify(centralSurfaces, times(0)).userActivity() - } - - @Test - @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun handleDispatchTouchEvent_nsslMigrationOn_userActivity() { underTest.setStatusBarViewController(phoneStatusBarViewController) @@ -438,7 +424,6 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : } @Test - @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun shouldInterceptTouchEvent_dozing_touchNotInLockIconArea_touchIntercepted() { // GIVEN dozing whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) @@ -451,7 +436,6 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : } @Test - @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun shouldInterceptTouchEvent_dozing_touchInStatusBar_touchIntercepted() { // GIVEN dozing whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) @@ -464,7 +448,6 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : } @Test - @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun shouldInterceptTouchEvent_dozingAndPulsing_touchIntercepted() { // GIVEN dozing whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) @@ -609,7 +592,6 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : } @Test - @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun cancelCurrentTouch_callsDragDownHelper() { underTest.cancelCurrentTouch() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt index 13bc82fa2c70..a04ca038021e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt @@ -16,7 +16,6 @@ package com.android.systemui.shade -import android.platform.test.annotations.DisableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View @@ -27,7 +26,6 @@ import androidx.annotation.IdRes import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT import com.android.systemui.SysuiTestCase import com.android.systemui.fragments.FragmentHostManager import com.android.systemui.fragments.FragmentService @@ -65,10 +63,7 @@ import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -/** - * Uses Flags.KEYGUARD_STATUS_VIEW_MIGRATE_NSSL set to false. If all goes well, this set of tests - * will be deleted. - */ +/** NotificationsQSContainerController tests */ @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest @@ -122,7 +117,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { delayableExecutor, notificationStackScrollLayoutController, ResourcesSplitShadeStateController(), - largeScreenHeaderHelperLazy = { largeScreenHeaderHelper } + largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }, ) overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN) @@ -209,23 +204,23 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { given( taskbarVisible = true, navigationMode = GESTURES_NAVIGATION, - insets = windowInsets().withStableBottom() + insets = windowInsets().withStableBottom(), ) then( expectedContainerPadding = 0, // taskbar should disappear when shade is expanded expectedNotificationsMargin = NOTIFICATIONS_MARGIN, - expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET + expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET, ) given( taskbarVisible = true, navigationMode = BUTTONS_NAVIGATION, - insets = windowInsets().withStableBottom() + insets = windowInsets().withStableBottom(), ) then( expectedContainerPadding = STABLE_INSET_BOTTOM, expectedNotificationsMargin = NOTIFICATIONS_MARGIN, - expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET + expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET, ) } @@ -237,22 +232,22 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { given( taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, - insets = windowInsets().withStableBottom() + insets = windowInsets().withStableBottom(), ) then( expectedContainerPadding = 0, - expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET + expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET, ) given( taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, - insets = windowInsets().withStableBottom() + insets = windowInsets().withStableBottom(), ) then( expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN, - expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET + expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET, ) } @@ -263,22 +258,22 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { given( taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, - insets = windowInsets().withCutout() + insets = windowInsets().withCutout(), ) then( expectedContainerPadding = CUTOUT_HEIGHT, - expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET + expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET, ) given( taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, - insets = windowInsets().withCutout().withStableBottom() + insets = windowInsets().withCutout().withStableBottom(), ) then( expectedContainerPadding = 0, expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN, - expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET + expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET, ) } @@ -289,18 +284,18 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { given( taskbarVisible = true, navigationMode = GESTURES_NAVIGATION, - insets = windowInsets().withStableBottom() + insets = windowInsets().withStableBottom(), ) then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM) given( taskbarVisible = true, navigationMode = BUTTONS_NAVIGATION, - insets = windowInsets().withStableBottom() + insets = windowInsets().withStableBottom(), ) then( expectedContainerPadding = STABLE_INSET_BOTTOM, - expectedQsPadding = STABLE_INSET_BOTTOM + expectedQsPadding = STABLE_INSET_BOTTOM, ) } @@ -314,19 +309,19 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { given( taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, - insets = windowInsets().withCutout().withStableBottom() + insets = windowInsets().withCutout().withStableBottom(), ) then(expectedContainerPadding = CUTOUT_HEIGHT, expectedQsPadding = STABLE_INSET_BOTTOM) given( taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, - insets = windowInsets().withStableBottom() + insets = windowInsets().withStableBottom(), ) then( expectedContainerPadding = 0, expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN, - expectedQsPadding = STABLE_INSET_BOTTOM + expectedQsPadding = STABLE_INSET_BOTTOM, ) } @@ -339,7 +334,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { given( taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, - insets = windowInsets().withStableBottom() + insets = windowInsets().withStableBottom(), ) then(expectedContainerPadding = 0, expectedNotificationsMargin = 0) @@ -355,7 +350,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { given( taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, - insets = windowInsets().withStableBottom() + insets = windowInsets().withStableBottom(), ) then(expectedContainerPadding = 0) @@ -376,43 +371,6 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { } @Test - @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun testSplitShadeLayout_isAlignedToGuideline() { - enableSplitShade() - underTest.updateResources() - assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline) - assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart) - .isEqualTo(R.id.qs_edge_guideline) - } - - @Test - @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun testSinglePaneLayout_childrenHaveEqualMargins() { - disableSplitShade() - underTest.updateResources() - val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin - val qsEndMargin = getConstraintSetLayout(R.id.qs_frame).endMargin - val notifStartMargin = getConstraintSetLayout(R.id.notification_stack_scroller).startMargin - val notifEndMargin = getConstraintSetLayout(R.id.notification_stack_scroller).endMargin - assertThat( - qsStartMargin == qsEndMargin && - notifStartMargin == notifEndMargin && - qsStartMargin == notifStartMargin - ) - .isTrue() - } - - @Test - @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() { - enableSplitShade() - underTest.updateResources() - assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0) - assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startMargin) - .isEqualTo(0) - } - - @Test fun testSplitShadeLayout_qsFrameHasHorizontalMarginsOfZero() { enableSplitShade() underTest.updateResources() @@ -421,37 +379,6 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { } @Test - @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeightHelper() { - setLargeScreen() - val largeScreenHeaderResourceHeight = 100 - val largeScreenHeaderHelperHeight = 200 - whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()) - .thenReturn(largeScreenHeaderHelperHeight) - overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderResourceHeight) - - // ensure the estimated height (would be 30 here) wouldn't impact this test case - overrideResource(R.dimen.large_screen_shade_header_min_height, 10) - overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 10) - - underTest.updateResources() - - assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin) - .isEqualTo(largeScreenHeaderHelperHeight) - assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin) - .isEqualTo(largeScreenHeaderHelperHeight) - } - - @Test - @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() { - setSmallScreen() - underTest.updateResources() - assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0) - assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin).isEqualTo(0) - } - - @Test fun testSinglePaneShadeLayout_qsFrameHasHorizontalMarginsSetToCorrectValue() { disableSplitShade() underTest.updateResources() @@ -464,17 +391,6 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { } @Test - @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun testSinglePaneShadeLayout_isAlignedToParent() { - disableSplitShade() - underTest.updateResources() - assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd) - .isEqualTo(ConstraintSet.PARENT_ID) - assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart) - .isEqualTo(ConstraintSet.PARENT_ID) - } - - @Test fun testAllChildrenOfNotificationContainer_haveIds() { // set dimen to 0 to avoid triggering updating bottom spacing overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, 0) @@ -493,7 +409,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { delayableExecutor, notificationStackScrollLayoutController, ResourcesSplitShadeStateController(), - largeScreenHeaderHelperLazy = { largeScreenHeaderHelper } + largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }, ) controller.updateConstraints() @@ -509,7 +425,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, insets = emptyInsets(), - applyImmediately = false + applyImmediately = false, ) fakeSystemClock.advanceTime(INSET_DEBOUNCE_MILLIS / 2) windowInsetsCallback.accept(windowInsets().withStableBottom()) @@ -576,7 +492,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { taskbarVisible: Boolean, navigationMode: Int, insets: WindowInsets, - applyImmediately: Boolean = true + applyImmediately: Boolean = true, ) { Mockito.clearInvocations(view) taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false) @@ -591,7 +507,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { fun then( expectedContainerPadding: Int, expectedNotificationsMargin: Int = NOTIFICATIONS_MARGIN, - expectedQsPadding: Int = 0 + expectedQsPadding: Int = 0, ) { verify(view).setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding)) verify(view).setNotificationsMarginBottom(expectedNotificationsMargin) @@ -623,7 +539,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { val layoutParams = ConstraintLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT + ViewGroup.LayoutParams.WRAP_CONTENT, ) // required as cloning ConstraintSet fails if view doesn't have layout params view.layoutParams = layoutParams diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt index 503fa789cb80..1eb88c5a5616 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt @@ -89,6 +89,7 @@ class SingleLineViewBinderTest : SysuiTestCase() { messagingStyle = null, builder = notificationBuilder, systemUiContext = context, + redactText = false, ) // WHEN: binds the viewHolder @@ -149,6 +150,7 @@ class SingleLineViewBinderTest : SysuiTestCase() { messagingStyle = style, builder = notificationBuilder, systemUiContext = context, + redactText = false, ) // WHEN: binds the view SingleLineViewBinder.bind(viewModel, view) @@ -197,6 +199,7 @@ class SingleLineViewBinderTest : SysuiTestCase() { messagingStyle = null, builder = notificationBuilder, systemUiContext = context, + redactText = false, ) // WHEN: binds the view with the view model SingleLineViewBinder.bind(viewModel, view) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt index d3666321c8e4..ef70e277832e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt @@ -379,7 +379,8 @@ class SingleLineViewInflaterTest : SysuiTestCase() { this, if (isConversation) messagingStyle else null, builder, - context + context, + false ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index a91fb45c9c80..e1a891662889 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -1544,14 +1544,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { assertFalse(mStackScroller.mHeadsUpAnimatingAway); } - @Test - @EnableSceneContainer - public void finishExpanding_sceneContainerEnabled() { - mStackScroller.startOverscrollAfterExpanding(); - verify(mStackScroller.getExpandHelper()).finishExpanding(); - assertTrue(mStackScroller.getIsBeingDragged()); - } - private MotionEvent captureTouchSentToSceneFramework() { ArgumentCaptor<MotionEvent> captor = ArgumentCaptor.forClass(MotionEvent.class); verify(mStackScrollLayoutController).sendTouchToSceneFramework(captor.capture()); diff --git a/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt index 76fc61185b49..25d1c377ecbd 100644 --- a/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt +++ b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt @@ -433,6 +433,8 @@ class FakeStatusBarService : IStatusBarService.Stub() { override fun showRearDisplayDialog(currentBaseState: Int) {} + override fun unbundleNotification(key: String) {} + companion object { const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY const val SECONDARY_DISPLAY_ID = 2 diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorKosmos.kt new file mode 100644 index 000000000000..57c8fd066ea8 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorKosmos.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.scene.domain.interactor.sceneInteractor + +val Kosmos.communalBackActionInteractor by + Kosmos.Fixture { + CommunalBackActionInteractor( + communalInteractor = communalInteractor, + communalSceneInteractor = communalSceneInteractor, + sceneInteractor = sceneInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt index ad92b318b0b9..bfc424848900 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt @@ -86,12 +86,8 @@ suspend fun Kosmos.setCommunalEnabled(enabled: Boolean) { } suspend fun Kosmos.setCommunalV2Enabled(enabled: Boolean) { - setCommunalV2ConfigEnabled(true) - if (enabled) { - fakeUserRepository.asMainUser() - } else { - fakeUserRepository.asDefaultUser() - } + setCommunalV2ConfigEnabled(enabled) + setCommunalEnabled(enabled) } suspend fun Kosmos.setCommunalAvailable(available: Boolean) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt index b407b1ba227a..e3cfb80489e3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt @@ -17,8 +17,10 @@ package com.android.systemui.communal.ui.viewmodel import android.service.dream.dreamManager +import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.plugins.activityStarter import com.android.systemui.statusbar.policy.batteryController val Kosmos.communalToDreamButtonViewModel by @@ -26,6 +28,8 @@ val Kosmos.communalToDreamButtonViewModel by CommunalToDreamButtonViewModel( backgroundContext = testDispatcher, batteryController = batteryController, + settingsInteractor = communalSettingsInteractor, + activityStarter = activityStarter, dreamManager = dreamManager, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt index 534ded57eb85..9012393badd6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt @@ -58,4 +58,26 @@ class FakeDisplayWindowPropertiesRepository(private val context: Context) : fun insert(instance: DisplayWindowProperties) { properties.put(instance.displayId, instance.windowType, instance) } + + /** inserts an entry, mocking everything except the context. */ + fun insertForContext(displayId: Int, windowType: Int, context: Context) { + properties.put( + displayId, + windowType, + DisplayWindowProperties( + displayId = displayId, + windowType = windowType, + context = context, + windowManager = mock(), + layoutInflater = mock(), + ), + ) + } + + /** Whether the repository contains an entry already. */ + fun contains(displayId: Int, windowType: Int): Boolean = + properties.contains(displayId, windowType) + + /** Removes all the entries. */ + fun clear() = properties.clear() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt index 548b5646f5d4..5d206691b520 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt @@ -30,7 +30,7 @@ class FakeKeyguardQuickAffordanceConfig( override val pickerIconResourceId: Int = 0, ) : KeyguardQuickAffordanceConfig { - var onTriggeredResult: OnTriggeredResult = OnTriggeredResult.Handled + var onTriggeredResult: OnTriggeredResult = OnTriggeredResult.Handled(false) private val _lockScreenState = MutableStateFlow<KeyguardQuickAffordanceConfig.LockScreenState>( @@ -41,9 +41,7 @@ class FakeKeyguardQuickAffordanceConfig( override fun pickerName(): String = pickerName - override fun onTriggered( - expandable: Expandable?, - ): OnTriggeredResult { + override fun onTriggered(expandable: Expandable?): OnTriggeredResult { return onTriggeredResult } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceHapticViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceHapticViewModelKosmos.kt new file mode 100644 index 000000000000..d857157137b6 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceHapticViewModelKosmos.kt @@ -0,0 +1,35 @@ +/* + * 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.keyguard.domain.interactor + +import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceHapticViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel +import com.android.systemui.kosmos.Kosmos +import kotlinx.coroutines.flow.Flow + +val Kosmos.keyguardQuickAffordanceHapticViewModelFactory by + Kosmos.Fixture { + object : KeyguardQuickAffordanceHapticViewModel.Factory { + override fun create( + quickAffordanceViewModel: Flow<KeyguardQuickAffordanceViewModel> + ): KeyguardQuickAffordanceHapticViewModel = + KeyguardQuickAffordanceHapticViewModel( + quickAffordanceViewModel, + keyguardQuickAffordanceInteractor, + ) + } + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt index e47310727905..abbfa93edd17 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt @@ -87,7 +87,6 @@ val Kosmos.keyguardRootViewModel by Fixture { primaryBouncerToLockscreenTransitionViewModel, screenOffAnimationController = screenOffAnimationController, aodBurnInViewModel = aodBurnInViewModel, - aodAlphaViewModel = aodAlphaViewModel, shadeInteractor = shadeInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt index d941fb049835..43835607c77d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt @@ -17,6 +17,7 @@ import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.mockito.kotlin.verify var Kosmos.testDispatcher by Fixture { StandardTestDispatcher() } @@ -82,6 +83,32 @@ fun <T> TestScope.currentValue(stateFlow: StateFlow<T>): T { } /** Retrieve the current value of this [StateFlow] safely. See `currentValue(TestScope)`. */ +fun <T> Kosmos.currentValue(fn: () -> T) = testScope.currentValue(fn) + +/** + * Retrieve the result of [fn] after running all pending tasks. Do not use to retrieve the value of + * a flow directly; for that, use either `currentValue(StateFlow)` or [collectLastValue] + */ +@OptIn(ExperimentalCoroutinesApi::class) +fun <T> TestScope.currentValue(fn: () -> T): T { + runCurrent() + return fn() +} + +/** Retrieve the result of [fn] after running all pending tasks. See `TestScope.currentValue(fn)` */ fun <T> Kosmos.currentValue(stateFlow: StateFlow<T>): T { return testScope.currentValue(stateFlow) } + +/** Safely verify that a mock has been called after the test scope has caught up */ +@OptIn(ExperimentalCoroutinesApi::class) +fun <T> TestScope.verifyCurrent(mock: T): T { + runCurrent() + return verify(mock) +} + +/** + * Safely verify that a mock has been called after the test scope has caught up. See + * `TestScope.verifyCurrent` + */ +fun <T> Kosmos.verifyCurrent(mock: T) = testScope.verifyCurrent(mock) 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 41cfceaa5e38..39f1ad42797b 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 @@ -63,6 +63,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.domain.startable.scrimStartable import com.android.systemui.scene.sceneContainerConfig import com.android.systemui.scene.shared.model.sceneDataSource +import com.android.systemui.scene.ui.view.mockWindowRootViewProvider import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.domain.interactor.shadeInteractor @@ -191,4 +192,5 @@ class KosmosJavaAdapter() { } val disableFlagsInteractor by lazy { kosmos.disableFlagsInteractor } val fakeDisableFlagsRepository by lazy { kosmos.fakeDisableFlagsRepository } + val mockWindowRootViewProvider by lazy { kosmos.mockWindowRootViewProvider } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt index 49bbbef6ccc0..0ec8d49ec29b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt @@ -17,6 +17,6 @@ package com.android.systemui.plugins import com.android.systemui.kosmos.Kosmos -import org.mockito.kotlin.mock +import com.android.systemui.util.mockito.mock var Kosmos.activityStarter by Kosmos.Fixture { mock<ActivityStarter>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt index f52572a9e42d..f5eebb46c2ec 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt @@ -19,13 +19,13 @@ package com.android.systemui.scene.shared.model import com.android.compose.animation.scene.OverlayKey import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey +import com.android.systemui.kosmos.currentValue import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.test.TestScope -class FakeSceneDataSource( - initialSceneKey: SceneKey, -) : SceneDataSource { +class FakeSceneDataSource(initialSceneKey: SceneKey, val testScope: TestScope) : SceneDataSource { private val _currentScene = MutableStateFlow(initialSceneKey) override val currentScene: StateFlow<SceneKey> = _currentScene.asStateFlow() @@ -33,18 +33,20 @@ class FakeSceneDataSource( private val _currentOverlays = MutableStateFlow<Set<OverlayKey>>(emptySet()) override val currentOverlays: StateFlow<Set<OverlayKey>> = _currentOverlays.asStateFlow() - var isPaused = false - private set + private var _isPaused = false + val isPaused + get() = testScope.currentValue { _isPaused } - var pendingScene: SceneKey? = null - private set + private var _pendingScene: SceneKey? = null + val pendingScene + get() = testScope.currentValue { _pendingScene } var pendingOverlays: Set<OverlayKey>? = null private set override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) { - if (isPaused) { - pendingScene = toScene + if (_isPaused) { + _pendingScene = toScene } else { _currentScene.value = toScene } @@ -55,7 +57,7 @@ class FakeSceneDataSource( } override fun showOverlay(overlay: OverlayKey, transitionKey: TransitionKey?) { - if (isPaused) { + if (_isPaused) { pendingOverlays = (pendingOverlays ?: currentOverlays.value) + overlay } else { _currentOverlays.value += overlay @@ -63,7 +65,7 @@ class FakeSceneDataSource( } override fun hideOverlay(overlay: OverlayKey, transitionKey: TransitionKey?) { - if (isPaused) { + if (_isPaused) { pendingOverlays = (pendingOverlays ?: currentOverlays.value) - overlay } else { _currentOverlays.value -= overlay @@ -82,9 +84,9 @@ class FakeSceneDataSource( * last one will be remembered. */ fun pause() { - check(!isPaused) { "Can't pause what's already paused!" } + check(!_isPaused) { "Can't pause what's already paused!" } - isPaused = true + _isPaused = true } /** @@ -100,15 +102,12 @@ class FakeSceneDataSource( * * If [expectedScene] is provided, will assert that it's indeed the latest called. */ - fun unpause( - force: Boolean = false, - expectedScene: SceneKey? = null, - ) { - check(force || isPaused) { "Can't unpause what's already not paused!" } - - isPaused = false - pendingScene?.let { _currentScene.value = it } - pendingScene = null + fun unpause(force: Boolean = false, expectedScene: SceneKey? = null) { + check(force || _isPaused) { "Can't unpause what's already not paused!" } + + _isPaused = false + _pendingScene?.let { _currentScene.value = it } + _pendingScene = null pendingOverlays?.let { _currentOverlays.value = it } pendingOverlays = null diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt index f5196866ae6f..7eebfc305682 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt @@ -19,13 +19,12 @@ package com.android.systemui.scene.shared.model import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testScope import com.android.systemui.scene.initialSceneKey import com.android.systemui.scene.sceneContainerConfig val Kosmos.fakeSceneDataSource by Fixture { - FakeSceneDataSource( - initialSceneKey = initialSceneKey, - ) + FakeSceneDataSource(initialSceneKey = initialSceneKey, testScope = testScope) } val Kosmos.sceneDataSourceDelegator by Fixture { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKosmos.kt index 5c91dc80b3d4..e6ba9a581836 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKosmos.kt @@ -17,6 +17,9 @@ package com.android.systemui.scene.ui.view import com.android.systemui.kosmos.Kosmos +import javax.inject.Provider import org.mockito.kotlin.mock val Kosmos.mockShadeRootView by Kosmos.Fixture { mock<WindowRootView>() } +val Kosmos.mockWindowRootViewProvider by + Kosmos.Fixture { Provider<WindowRootView> { mock<WindowRootView>() } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt index 4dcd2208b152..3ed730271bc3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt @@ -16,6 +16,14 @@ package com.android.systemui.shade.data.repository +import android.content.applicationContext import com.android.systemui.kosmos.Kosmos +import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock val Kosmos.shadeAnimationRepository by Kosmos.Fixture { ShadeAnimationRepository() } +val Kosmos.shadeDialogContextInteractor by + Kosmos.Fixture { + mock<ShadeDialogContextInteractor> { on { context } doReturn applicationContext } + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionControllerKosmos.kt index 8865573c4030..e5a75d59468d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionControllerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionControllerKosmos.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 The Android Open Source Project + * 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. @@ -18,14 +18,8 @@ package com.android.systemui.statusbar import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import org.mockito.kotlin.mock - -var Kosmos.lockscreenShadeKeyguardTransitionController by Fixture { - mock<LockscreenShadeKeyguardTransitionController>() -} +import com.android.systemui.util.mockito.mock var Kosmos.lockscreenShadeKeyguardTransitionControllerFactory by Fixture { - LockscreenShadeKeyguardTransitionController.Factory { - lockscreenShadeKeyguardTransitionController - } + mock<LockscreenShadeKeyguardTransitionController.Factory>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerKosmos.kt index fc52e454a1c6..27679804d11f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerKosmos.kt @@ -18,12 +18,8 @@ package com.android.systemui.statusbar import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import org.mockito.kotlin.mock - -var Kosmos.lockscreenShadeQsTransitionController by Fixture { - mock<LockscreenShadeQsTransitionController>() -} +import com.android.systemui.util.mockito.mock var Kosmos.lockscreenShadeQsTransitionControllerFactory by Fixture { - LockscreenShadeQsTransitionController.Factory { lockscreenShadeQsTransitionController } + mock<LockscreenShadeQsTransitionController.Factory>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerKosmos.kt index 5523bd68f692..43e39c05f6e9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerKosmos.kt @@ -18,12 +18,8 @@ package com.android.systemui.statusbar import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import org.mockito.kotlin.mock - -var Kosmos.singleShadeLockScreenOverScroller by Fixture { - mock<SingleShadeLockScreenOverScroller>() -} +import com.android.systemui.util.mockito.mock var Kosmos.singleShadeLockScreenOverScrollerFactory by Fixture { - SingleShadeLockScreenOverScroller.Factory { _ -> singleShadeLockScreenOverScroller } + mock<SingleShadeLockScreenOverScroller.Factory>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerKosmos.kt index e491dffb0ed5..017371a6cba8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerKosmos.kt @@ -18,10 +18,8 @@ package com.android.systemui.statusbar import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import org.mockito.kotlin.mock - -var Kosmos.splitShadeLockScreenOverScroller by Fixture { mock<SplitShadeLockScreenOverScroller>() } +import com.android.systemui.util.mockito.mock var Kosmos.splitShadeLockScreenOverScrollerFactory by Fixture { - SplitShadeLockScreenOverScroller.Factory { _, _ -> splitShadeLockScreenOverScroller } + mock<SplitShadeLockScreenOverScroller.Factory>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt index 768952d1ee77..62cdc87f980f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt @@ -14,9 +14,10 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.domain.interactor +package com.android.systemui.statusbar.featurepods.popups.ui.viewmodel import com.android.systemui.kosmos.Kosmos -import org.mockito.kotlin.mock +import com.android.systemui.kosmos.testScope -val Kosmos.notificationAlertsInteractor by Kosmos.Fixture { mock<NotificationAlertsInteractor>() } +val Kosmos.statusBarPopupChipsViewModel: StatusBarPopupChipsViewModel by + Kosmos.Fixture { StatusBarPopupChipsViewModel(testScope.backgroundScope) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationContentExtractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationContentExtractor.kt new file mode 100644 index 000000000000..680e0de22794 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationContentExtractor.kt @@ -0,0 +1,58 @@ +/* + * 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.notification.promoted + +import android.app.Notification +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel +import org.junit.Assert + +class FakePromotedNotificationContentExtractor : PromotedNotificationContentExtractor { + @JvmField + val contentForEntry = mutableMapOf<NotificationEntry, PromotedNotificationContentModel?>() + @JvmField val extractCalls = mutableListOf<Pair<NotificationEntry, Notification.Builder>>() + + override fun extractContent( + entry: NotificationEntry, + recoveredBuilder: Notification.Builder, + ): PromotedNotificationContentModel? { + extractCalls.add(entry to recoveredBuilder) + + if (contentForEntry.isEmpty()) { + // If *no* entries are set, just return null for everything. + return null + } else { + // If entries *are* set, fail on unexpected ones. + Assert.assertTrue(contentForEntry.containsKey(entry)) + return contentForEntry.get(entry) + } + } + + fun resetForEntry(entry: NotificationEntry, content: PromotedNotificationContentModel?) { + contentForEntry.clear() + contentForEntry.put(entry, content) + extractCalls.clear() + } + + fun verifyZeroExtractCalls() { + Assert.assertTrue(extractCalls.isEmpty()) + } + + fun verifyOneExtractCall() { + Assert.assertEquals(1, extractCalls.size) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationsProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationsProvider.kt index 88caf6e2ba30..ea7b41d43871 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationsProvider.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationsProvider.kt @@ -17,11 +17,20 @@ package com.android.systemui.statusbar.notification.promoted import com.android.systemui.statusbar.notification.collection.NotificationEntry +import org.junit.Assert class FakePromotedNotificationsProvider : PromotedNotificationsProvider { val promotedEntries = mutableSetOf<NotificationEntry>() + val shouldPromoteForEntry = mutableMapOf<NotificationEntry, Boolean>() override fun shouldPromote(entry: NotificationEntry): Boolean { - return promotedEntries.contains(entry) + if (shouldPromoteForEntry.isEmpty()) { + // If *no* entries are set, just return false for everything. + return false + } else { + // If entries *are* set, fail on unexpected ones. + Assert.assertTrue(shouldPromoteForEntry.containsKey(entry)) + return shouldPromoteForEntry[entry] ?: false + } } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt index 5e9f12b4b1cc..52c17c82fb12 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt @@ -21,7 +21,7 @@ import com.android.systemui.kosmos.Kosmos var Kosmos.promotedNotificationContentExtractor by Kosmos.Fixture { - PromotedNotificationContentExtractor( + PromotedNotificationContentExtractorImpl( promotedNotificationsProvider, applicationContext, promotedNotificationLogger, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt index 7126933154df..e739e82aa8a8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt @@ -65,7 +65,7 @@ import com.android.systemui.statusbar.notification.headsup.HeadsUpManager import com.android.systemui.statusbar.notification.icon.IconBuilder import com.android.systemui.statusbar.notification.icon.IconManager import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier -import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractorImpl import com.android.systemui.statusbar.notification.promoted.PromotedNotificationLogger import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsProviderImpl import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.CoordinateOnClickListener @@ -222,16 +222,11 @@ class ExpandableNotificationRowBuilder( Mockito.mock(LauncherApps::class.java, STUB_ONLY), Mockito.mock(ConversationNotificationManager::class.java, STUB_ONLY), ) - - val promotedNotificationsProvider = PromotedNotificationsProviderImpl() - val promotedNotificationLog = logcatLogBuffer("PromotedNotifLog") - val promotedNotificationLogger = PromotedNotificationLogger(promotedNotificationLog) - val promotedNotificationContentExtractor = - PromotedNotificationContentExtractor( - promotedNotificationsProvider, + PromotedNotificationContentExtractorImpl( + PromotedNotificationsProviderImpl(), context, - promotedNotificationLogger, + PromotedNotificationLogger(logcatLogBuffer("PromotedNotifLog")), ) mContentBinder = diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt index 26642d4f7534..f19ac1e5a58d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.policy import com.android.systemui.kosmos.Kosmos -import org.mockito.kotlin.mock +import org.mockito.Mockito.mock var Kosmos.keyguardStateController: KeyguardStateController by - Kosmos.Fixture { mock<KeyguardStateController>() } + Kosmos.Fixture { mock(KeyguardStateController::class.java) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt index 932e768676cb..6c98d19db5d7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt @@ -20,6 +20,7 @@ import com.android.systemui.animation.dialogTransitionAnimator import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.mainCoroutineContext import com.android.systemui.plugins.activityStarter +import com.android.systemui.shade.data.repository.shadeDialogContextInteractor import com.android.systemui.statusbar.phone.systemUIDialogFactory import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.modesDialogViewModel import com.android.systemui.util.mockito.mock @@ -35,5 +36,6 @@ var Kosmos.modesDialogDelegate: ModesDialogDelegate by { modesDialogViewModel }, modesDialogEventLogger, mainCoroutineContext, + shadeDialogContextInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt index a3572754ab19..f31697e82a45 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt @@ -70,6 +70,14 @@ class FakeSettings : SecureSettings, SystemSettings, UserSettingsProxy { } } + fun getContentObservers(uri: Uri, userHandle: Int): List<ContentObserver> { + if (userHandle == UserHandle.USER_ALL) { + return contentObserversAllUsers[uri.toString()] ?: listOf() + } else { + return contentObservers[SettingsKey(userHandle, uri.toString())] ?: listOf() + } + } + override fun getContentResolver(): ContentResolver { throw UnsupportedOperationException("FakeSettings.getContentResolver is not implemented") } diff --git a/packages/Vcn/framework-b/src/android/net/vcn/VcnTransportInfo.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnTransportInfo.java index 3638429f33fb..a760b12ac8a1 100644 --- a/packages/Vcn/framework-b/src/android/net/vcn/VcnTransportInfo.java +++ b/packages/Vcn/framework-b/src/android/net/vcn/VcnTransportInfo.java @@ -161,7 +161,14 @@ public final class VcnTransportInfo implements TransportInfo, Parcelable { return 0; } - /** @hide */ + /** + * Create a copy of a {@link VcnTransportInfo} with some fields redacted based on the + * permissions held by the receiving app. + * + * @param redactions bitmask of redactions that needs to be performed on this instance. + * @return the copy of this instance with the necessary redactions. + * @hide + */ @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API) @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @Override diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 3441d94facda..9ceca5d1dbfe 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -1589,7 +1589,13 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ * lock because this calls out to WindowManagerService. */ void addWindowTokensForAllDisplays() { - final Display[] displays = mDisplayManager.getDisplays(); + Display[] displays = {}; + final long identity = Binder.clearCallingIdentity(); + try { + displays = mDisplayManager.getDisplays(); + } finally { + Binder.restoreCallingIdentity(identity); + } for (int i = 0; i < displays.length; i++) { final int displayId = displays[i].getDisplayId(); addWindowTokenForDisplay(displayId); @@ -1625,7 +1631,13 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } public void onRemoved() { - final Display[] displays = mDisplayManager.getDisplays(); + Display[] displays = {}; + final long identity = Binder.clearCallingIdentity(); + try { + displays = mDisplayManager.getDisplays(); + } finally { + Binder.restoreCallingIdentity(identity); + } for (int i = 0; i < displays.length; i++) { final int displayId = displays[i].getDisplayId(); onDisplayRemoved(displayId); diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 3025e2eaede0..549f8fa77b53 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -217,6 +217,13 @@ public class UserBackupManagerService { + mPowerManagerWakeLock.getTag())); return; } + + if (!mPowerManagerWakeLock.isHeld()) { + Slog.w(TAG, addUserIdToLogMessage(mUserId, + "Wakelock not held: " + mPowerManagerWakeLock.getTag())); + return; + } + mPowerManagerWakeLock.release(); Slog.v( TAG, diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java index fd18fa856916..abfb8268bd9a 100644 --- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java +++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java @@ -55,6 +55,7 @@ import android.content.Intent; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.graphics.Bitmap; +import android.media.AudioManager; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -102,6 +103,7 @@ public class ContextualSearchManagerService extends SystemService { private final PackageManagerInternal mPackageManager; private final WindowManagerInternal mWmInternal; private final DevicePolicyManagerInternal mDpmInternal; + private final AudioManager mAudioManager; private final Object mLock = new Object(); private final AssistDataRequester mAssistDataRequester; @@ -163,6 +165,8 @@ public class ContextualSearchManagerService extends SystemService { mAtmInternal = Objects.requireNonNull( LocalServices.getService(ActivityTaskManagerInternal.class)); mPackageManager = LocalServices.getService(PackageManagerInternal.class); + mAudioManager = context.getSystemService(AudioManager.class); + mWmInternal = Objects.requireNonNull(LocalServices.getService(WindowManagerInternal.class)); mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class); mAssistDataRequester = new AssistDataRequester( @@ -306,6 +310,10 @@ public class ContextualSearchManagerService extends SystemService { SystemClock.uptimeMillis()); launchIntent.putExtra(ContextualSearchManager.EXTRA_ENTRYPOINT, entrypoint); launchIntent.putExtra(ContextualSearchManager.EXTRA_TOKEN, mToken); + if (Flags.includeAudioPlayingStatus()) { + launchIntent.putExtra(ContextualSearchManager.EXTRA_IS_AUDIO_PLAYING, + mAudioManager.isMusicActive()); + } boolean isAssistDataAllowed = mAtmInternal.isAssistDataAllowed(); final List<ActivityAssistInfo> records = mAtmInternal.getTopVisibleActivities(); final List<IBinder> activityTokens = new ArrayList<>(records.size()); diff --git a/services/core/Android.bp b/services/core/Android.bp index 06f9e2bf55b2..dc830642dcc5 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -229,7 +229,6 @@ java_library_static { "power_hint_flags_lib", "biometrics_flags_lib", "am_flags_lib", - "updates_flags_lib", "com_android_server_accessibility_flags_lib", "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib", "com_android_launcher3_flags_lib", diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 4944cafeb83d..4b8770b3cd35 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -16,6 +16,7 @@ package com.android.server.am; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RECONFIGURATION; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION; @@ -31,6 +32,7 @@ import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROA import static com.android.server.am.BroadcastConstants.getDeviceConfigBoolean; import android.annotation.NonNull; +import android.app.ActivityManagerInternal; import android.app.ActivityThread; import android.app.ForegroundServiceTypePolicy; import android.content.ComponentName; @@ -55,6 +57,7 @@ import android.util.SparseBooleanArray; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.server.LocalServices; import dalvik.annotation.optimization.NeverCompile; @@ -181,6 +184,12 @@ final class ActivityManagerConstants extends ContentObserver { static final String KEY_FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION = "follow_up_oomadj_update_wait_duration"; + /* + * Oom score cutoff beyond which any process that does not have the CPU_TIME capability will be + * frozen. + */ + static final String KEY_FREEZER_CUTOFF_ADJ = "freezer_cutoff_adj"; + private static final int DEFAULT_MAX_CACHED_PROCESSES = 1024; private static final boolean DEFAULT_PRIORITIZE_ALARM_BROADCASTS = true; private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000; @@ -267,6 +276,9 @@ final class ActivityManagerConstants extends ContentObserver { */ private static final long DEFAULT_FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION = 1000L; + /** The default value to {@link #KEY_FREEZER_CUTOFF_ADJ} */ + private static final int DEFAULT_FREEZER_CUTOFF_ADJ = ProcessList.CACHED_APP_MIN_ADJ; + /** * Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED} */ @@ -1171,6 +1183,14 @@ final class ActivityManagerConstants extends ContentObserver { DEFAULT_FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION; /** + * The cutoff adj for the freezer, app processes with adj greater than this value will be + * eligible for the freezer. + * + * @see #KEY_FREEZER_CUTOFF_ADJ + */ + public int FREEZER_CUTOFF_ADJ = DEFAULT_FREEZER_CUTOFF_ADJ; + + /** * Indicates whether PSS profiling in AppProfiler is disabled or not. */ static final String KEY_DISABLE_APP_PROFILER_PSS_PROFILING = @@ -1194,6 +1214,7 @@ final class ActivityManagerConstants extends ContentObserver { new OnPropertiesChangedListener() { @Override public void onPropertiesChanged(Properties properties) { + boolean oomAdjusterConfigUpdated = false; for (String name : properties.getKeyset()) { if (name == null) { return; @@ -1372,6 +1393,11 @@ final class ActivityManagerConstants extends ContentObserver { case KEY_TIERED_CACHED_ADJ_UI_TIER_SIZE: updateUseTieredCachedAdj(); break; + case KEY_FREEZER_CUTOFF_ADJ: + FREEZER_CUTOFF_ADJ = properties.getInt(KEY_FREEZER_CUTOFF_ADJ, + DEFAULT_FREEZER_CUTOFF_ADJ); + oomAdjusterConfigUpdated = true; + break; case KEY_DISABLE_APP_PROFILER_PSS_PROFILING: updateDisableAppProfilerPssProfiling(); break; @@ -1389,6 +1415,13 @@ final class ActivityManagerConstants extends ContentObserver { break; } } + if (oomAdjusterConfigUpdated) { + final ActivityManagerInternal ami = LocalServices.getService( + ActivityManagerInternal.class); + if (ami != null) { + ami.updateOomAdj(OOM_ADJ_REASON_RECONFIGURATION); + } + } } }; @@ -2534,6 +2567,9 @@ final class ActivityManagerConstants extends ContentObserver { pw.print(" "); pw.print(KEY_ENABLE_NEW_OOMADJ); pw.print("="); pw.println(ENABLE_NEW_OOMADJ); + pw.print(" "); pw.print(KEY_FREEZER_CUTOFF_ADJ); + pw.print("="); pw.println(FREEZER_CUTOFF_ADJ); + pw.print(" "); pw.print(KEY_DISABLE_APP_PROFILER_PSS_PROFILING); pw.print("="); pw.println(APP_PROFILER_PSS_PROFILING_DISABLED); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index cd929c1883d0..50b6990c0c1c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -4799,9 +4799,6 @@ public class ActivityManagerService extends IActivityManager.Stub updateLruProcessLocked(app, false, null); checkTime(startTime, "attachApplicationLocked: after updateLruProcessLocked"); - updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN); - checkTime(startTime, "attachApplicationLocked: after updateOomAdjLocked"); - final long now = SystemClock.uptimeMillis(); synchronized (mAppProfiler.mProfilerLock) { app.mProfile.setLastRequestedGc(now); @@ -4815,6 +4812,15 @@ public class ActivityManagerService extends IActivityManager.Stub } mProcessesOnHold.remove(app); + // See if the top visible activity is waiting to run in this process... + if (com.android.server.am.Flags.expediteActivityLaunchOnColdStart()) { + if (normalMode) { + mAtmInternal.attachApplication(app.getWindowProcessController()); + } + } + updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN); + checkTime(startTime, "attachApplicationLocked: after updateOomAdjLocked"); + if (!mConstants.mEnableWaitForFinishAttachApplication) { finishAttachApplicationInner(startSeq, callingUid, pid); } @@ -4880,18 +4886,21 @@ public class ActivityManagerService extends IActivityManager.Stub // Mark the finish attach application phase as completed mProcessStateController.setPendingFinishAttach(app, false); - final boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info); final String processName = app.processName; boolean badApp = false; boolean didSomething = false; - // See if the top visible activity is waiting to run in this process... - if (normalMode) { - try { - didSomething = mAtmInternal.attachApplication(app.getWindowProcessController()); - } catch (Exception e) { - Slog.wtf(TAG, "Exception thrown launching activities in " + app, e); - badApp = true; + if (!com.android.server.am.Flags.expediteActivityLaunchOnColdStart()) { + final boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info); + + if (normalMode) { + try { + didSomething |= mAtmInternal.attachApplication( + app.getWindowProcessController()); + } catch (Exception e) { + Slog.wtf(TAG, "Exception thrown launching activities in " + app, e); + badApp = true; + } } } @@ -19387,9 +19396,6 @@ public class ActivityManagerService extends IActivityManager.Stub creatorPackage); if (creatorToken != null) { extraIntent.setCreatorToken(creatorToken); - // TODO remove Slog.wtf once proven FrameworkStatsLog works. b/375396329 - Slog.wtf(TAG, "A creator token is added to an intent. creatorPackage: " - + creatorPackage + "; intent: " + extraIntent); FrameworkStatsLog.write(INTENT_CREATOR_TOKEN_ADDED, creatorUid, false); } }); diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index 2f5362f53361..d335529a006a 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -25,9 +25,11 @@ import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FOLLOW_UP; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RECONFIGURATION; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE; @@ -203,6 +205,10 @@ public class CachedAppOptimizer { FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_RESTRICTION_CHANGE; static final int UNFREEZE_REASON_COMPONENT_DISABLED = FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_COMPONENT_DISABLED; + static final int UNFREEZE_REASON_OOM_ADJ_FOLLOW_UP = + FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_OOM_ADJ_FOLLOW_UP; + static final int UNFREEZE_REASON_OOM_ADJ_RECONFIGURATION = + FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_OOM_ADJ_RECONFIGURATION; @IntDef(prefix = {"UNFREEZE_REASON_"}, value = { UNFREEZE_REASON_NONE, @@ -234,6 +240,8 @@ public class CachedAppOptimizer { UNFREEZE_REASON_EXECUTING_SERVICE, UNFREEZE_REASON_RESTRICTION_CHANGE, UNFREEZE_REASON_COMPONENT_DISABLED, + UNFREEZE_REASON_OOM_ADJ_FOLLOW_UP, + UNFREEZE_REASON_OOM_ADJ_RECONFIGURATION, }) @Retention(RetentionPolicy.SOURCE) public @interface UnfreezeReason {} @@ -2451,8 +2459,8 @@ public class CachedAppOptimizer { synchronized (mAm.mPidsSelfLocked) { pr = mAm.mPidsSelfLocked.get(blocked); } - if (pr != null - && pr.mState.getCurAdj() < ProcessList.FREEZER_CUTOFF_ADJ) { + if (pr != null && pr.mState.getCurAdj() + < mAm.mConstants.FREEZER_CUTOFF_ADJ) { Slog.d(TAG_AM, app.processName + " (" + pid + ") blocks " + pr.processName + " (" + blocked + ")"); // Found at least one blocked non-cached process @@ -2539,6 +2547,10 @@ public class CachedAppOptimizer { return UNFREEZE_REASON_RESTRICTION_CHANGE; case OOM_ADJ_REASON_COMPONENT_DISABLED: return UNFREEZE_REASON_COMPONENT_DISABLED; + case OOM_ADJ_REASON_FOLLOW_UP: + return UNFREEZE_REASON_OOM_ADJ_FOLLOW_UP; + case OOM_ADJ_REASON_RECONFIGURATION: + return UNFREEZE_REASON_OOM_ADJ_RECONFIGURATION; default: return UNFREEZE_REASON_NONE; } diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index aadf6f61956c..9c569db99797 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -55,6 +55,7 @@ import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RECONFIGURATION; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE; @@ -105,7 +106,6 @@ import static com.android.server.am.ProcessList.CACHED_APP_IMPORTANCE_LEVELS; import static com.android.server.am.ProcessList.CACHED_APP_MAX_ADJ; import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ; import static com.android.server.am.ProcessList.FOREGROUND_APP_ADJ; -import static com.android.server.am.ProcessList.FREEZER_CUTOFF_ADJ; import static com.android.server.am.ProcessList.HEAVY_WEIGHT_APP_ADJ; import static com.android.server.am.ProcessList.HOME_APP_ADJ; import static com.android.server.am.ProcessList.INVALID_ADJ; @@ -232,6 +232,8 @@ public class OomAdjuster { return AppProtoEnums.OOM_ADJ_REASON_COMPONENT_DISABLED; case OOM_ADJ_REASON_FOLLOW_UP: return AppProtoEnums.OOM_ADJ_REASON_FOLLOW_UP; + case OOM_ADJ_REASON_RECONFIGURATION: + return AppProtoEnums.OOM_ADJ_REASON_RECONFIGURATION; default: return AppProtoEnums.OOM_ADJ_REASON_UNKNOWN_TO_PROTO; } @@ -288,6 +290,8 @@ public class OomAdjuster { return OOM_ADJ_REASON_METHOD + "_componentDisabled"; case OOM_ADJ_REASON_FOLLOW_UP: return OOM_ADJ_REASON_METHOD + "_followUp"; + case OOM_ADJ_REASON_RECONFIGURATION: + return OOM_ADJ_REASON_METHOD + "_reconfiguration"; default: return "_unknown"; } @@ -4079,7 +4083,7 @@ public class OomAdjuster { } // Reasons to freeze: - if (proc.mState.getCurAdj() >= FREEZER_CUTOFF_ADJ) { + if (proc.mState.getCurAdj() >= mConstants.FREEZER_CUTOFF_ADJ) { // Oomscore is in a high enough state, it is safe to freeze. return true; } @@ -4098,9 +4102,8 @@ public class OomAdjuster { final ProcessCachedOptimizerRecord opt = app.mOptRecord; final ProcessStateRecord state = app.mState; if (Flags.traceUpdateAppFreezeStateLsp()) { - final boolean oomAdjChanged = - (state.getCurAdj() >= FREEZER_CUTOFF_ADJ ^ oldOomAdj >= FREEZER_CUTOFF_ADJ) - || oldOomAdj == UNKNOWN_ADJ; + final boolean oomAdjChanged = (state.getCurAdj() >= mConstants.FREEZER_CUTOFF_ADJ + ^ oldOomAdj >= mConstants.FREEZER_CUTOFF_ADJ) || oldOomAdj == UNKNOWN_ADJ; final boolean shouldNotFreezeChanged = opt.shouldNotFreezeAdjSeq() == mAdjSeq; final boolean hasCpuCapability = (PROCESS_CAPABILITY_CPU_TIME & app.mState.getCurCapability()) diff --git a/services/core/java/com/android/server/am/PhantomProcessList.java b/services/core/java/com/android/server/am/PhantomProcessList.java index 123780fb7567..99bdd10ff71b 100644 --- a/services/core/java/com/android/server/am/PhantomProcessList.java +++ b/services/core/java/com/android/server/am/PhantomProcessList.java @@ -112,23 +112,10 @@ public final class PhantomProcessList { private final ActivityManagerService mService; private final Handler mKillHandler; - private static final int CGROUP_V1 = 0; - private static final int CGROUP_V2 = 1; - private static final String[] CGROUP_PATH_PREFIXES = { - "/acct/uid_" /* cgroup v1 */, - "/sys/fs/cgroup/uid_" /* cgroup v2 */ - }; - private static final String CGROUP_PID_PREFIX = "/pid_"; - private static final String CGROUP_PROCS = "/cgroup.procs"; - - @VisibleForTesting - int mCgroupVersion = CGROUP_V1; - PhantomProcessList(final ActivityManagerService service) { mService = service; mKillHandler = service.mProcessList.sKillHandler; mInjector = new Injector(); - probeCgroupVersion(); } @VisibleForTesting @@ -157,9 +144,15 @@ public final class PhantomProcessList { final int appPid = app.getPid(); InputStream input = mCgroupProcsFds.get(appPid); if (input == null) { - final String path = getCgroupFilePath(app.info.uid, appPid); + String path = null; try { + path = getCgroupFilePath(app.info.uid, appPid); input = mInjector.openCgroupProcs(path); + } catch (IllegalArgumentException e) { + if (DEBUG_PROCESSES) { + Slog.w(TAG, "Unable to obtain cgroup.procs path ", e); + } + return; } catch (FileNotFoundException | SecurityException e) { if (DEBUG_PROCESSES) { Slog.w(TAG, "Unable to open " + path, e); @@ -207,18 +200,9 @@ public final class PhantomProcessList { } } - private void probeCgroupVersion() { - for (int i = CGROUP_PATH_PREFIXES.length - 1; i >= 0; i--) { - if ((new File(CGROUP_PATH_PREFIXES[i] + Process.SYSTEM_UID)).exists()) { - mCgroupVersion = i; - break; - } - } - } - @VisibleForTesting String getCgroupFilePath(int uid, int pid) { - return CGROUP_PATH_PREFIXES[mCgroupVersion] + uid + CGROUP_PID_PREFIX + pid + CGROUP_PROCS; + return nativeGetCgroupProcsPath(uid, pid); } static String getProcessName(int pid) { @@ -630,4 +614,7 @@ public final class PhantomProcessList { return PhantomProcessList.getProcessName(pid); } } + + private static native String nativeGetCgroupProcsPath(int uid, int pid) + throws IllegalArgumentException; } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 70febcd63455..bddde9d589f3 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -383,12 +383,6 @@ public final class ProcessList { private static final long LMKD_RECONNECT_DELAY_MS = 1000; /** - * The cuttoff adj for the freezer, app processes with adj greater than this value will be - * eligible for the freezer. - */ - static final int FREEZER_CUTOFF_ADJ = CACHED_APP_MIN_ADJ; - - /** * Apps have no access to the private data directories of any other app, even if the other * app has made them world-readable. */ diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 98f738c38d63..49149e1fa415 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -1699,7 +1699,7 @@ class ProcessRecord implements WindowProcessListener { return mService.mOomAdjuster.mCachedAppOptimizer.useFreezer() && !mOptRecord.isFreezeExempt() && !mOptRecord.shouldNotFreeze() - && mState.getCurAdj() >= ProcessList.FREEZER_CUTOFF_ADJ; + && mState.getCurAdj() >= mService.mConstants.FREEZER_CUTOFF_ADJ; } public void forEachConnectionHost(Consumer<ProcessRecord> consumer) { diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 87f87c76725e..c82933c5069e 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -158,6 +158,7 @@ public class SettingsToPropertiesMapper { "aoc", "app_widgets", "arc_next", + "art_cloud", "art_mainline", "art_performance", "attack_tools", diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index c31b9ef60bd2..70f2a8e1dd1b 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -160,6 +160,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; import java.util.function.Consumer; /** @@ -176,6 +177,9 @@ import java.util.function.Consumer; class UserController implements Handler.Callback { private static final String TAG = TAG_WITH_CLASS_NAME ? "UserController" : TAG_AM; + // Amount of time we wait for observers to handle onBeforeUserSwitching, before crashing system. + static final int DEFAULT_BEFORE_USER_SWITCH_TIMEOUT_MS = 20 * 1000; + // Amount of time we wait for observers to handle a user switch before // giving up on them and dismissing the user switching dialog. static final int DEFAULT_USER_SWITCH_TIMEOUT_MS = 3 * 1000; @@ -1920,8 +1924,14 @@ class UserController implements Handler.Callback { return false; } - mHandler.post(() -> startUserInternalOnHandler(userId, oldUserId, userStartMode, - unlockListener, callingUid, callingPid)); + final Runnable continueStartUserInternal = () -> continueStartUserInternal(userInfo, + oldUserId, userStartMode, unlockListener, callingUid, callingPid); + if (foreground) { + mHandler.post(() -> dispatchOnBeforeUserSwitching(userId, () -> + mHandler.post(continueStartUserInternal))); + } else { + continueStartUserInternal.run(); + } } finally { Binder.restoreCallingIdentity(ident); } @@ -1929,11 +1939,11 @@ class UserController implements Handler.Callback { return true; } - private void startUserInternalOnHandler(int userId, int oldUserId, int userStartMode, + private void continueStartUserInternal(UserInfo userInfo, int oldUserId, int userStartMode, IProgressListener unlockListener, int callingUid, int callingPid) { final TimingsTraceAndSlog t = new TimingsTraceAndSlog(); final boolean foreground = userStartMode == USER_START_MODE_FOREGROUND; - final UserInfo userInfo = getUserInfo(userId); + final int userId = userInfo.id; boolean needStart = false; boolean updateUmState = false; @@ -1995,7 +2005,6 @@ class UserController implements Handler.Callback { // it should be moved outside, but for now it's not as there are many calls to // external components here afterwards updateProfileRelatedCaches(); - dispatchOnBeforeUserSwitching(userId); mInjector.getWindowManager().setCurrentUser(userId); mInjector.reportCurWakefulnessUsageEvent(); // Once the internal notion of the active user has switched, we lock the device @@ -2296,25 +2305,40 @@ class UserController implements Handler.Callback { mUserSwitchObservers.finishBroadcast(); } - private void dispatchOnBeforeUserSwitching(@UserIdInt int newUserId) { + private void dispatchOnBeforeUserSwitching(@UserIdInt int newUserId, Runnable onComplete) { final TimingsTraceAndSlog t = new TimingsTraceAndSlog(); t.traceBegin("dispatchOnBeforeUserSwitching-" + newUserId); - final int observerCount = mUserSwitchObservers.beginBroadcast(); - for (int i = 0; i < observerCount; i++) { - final String name = "#" + i + " " + mUserSwitchObservers.getBroadcastCookie(i); - t.traceBegin("onBeforeUserSwitching-" + name); + final AtomicBoolean isSuccessful = new AtomicBoolean(false); + startTimeoutForOnBeforeUserSwitching(isSuccessful); + informUserSwitchObservers((observer, callback) -> { try { - mUserSwitchObservers.getBroadcastItem(i).onBeforeUserSwitching(newUserId); + observer.onBeforeUserSwitching(newUserId, callback); } catch (RemoteException e) { - // Ignore - } finally { - t.traceEnd(); + // ignore } - } - mUserSwitchObservers.finishBroadcast(); + }, () -> { + isSuccessful.set(true); + onComplete.run(); + }, "onBeforeUserSwitching"); t.traceEnd(); } + private void startTimeoutForOnBeforeUserSwitching(AtomicBoolean isSuccessful) { + mHandler.postDelayed(() -> { + if (isSuccessful.get()) { + return; + } + String unresponsiveObservers; + synchronized (mLock) { + unresponsiveObservers = String.join(", ", mCurWaitingUserSwitchCallbacks); + } + throw new RuntimeException("Timeout on dispatchOnBeforeUserSwitching. " + + "These UserSwitchObservers did not respond in " + + DEFAULT_BEFORE_USER_SWITCH_TIMEOUT_MS + "ms: " + unresponsiveObservers + "."); + }, DEFAULT_BEFORE_USER_SWITCH_TIMEOUT_MS); + } + + /** Called on handler thread */ @VisibleForTesting void dispatchUserSwitchComplete(@UserIdInt int oldUserId, @UserIdInt int newUserId) { @@ -2527,70 +2551,76 @@ class UserController implements Handler.Callback { t.traceBegin("dispatchUserSwitch-" + oldUserId + "-to-" + newUserId); EventLog.writeEvent(EventLogTags.UC_DISPATCH_USER_SWITCH, oldUserId, newUserId); + uss.switching = true; + informUserSwitchObservers((observer, callback) -> { + try { + observer.onUserSwitching(newUserId, callback); + } catch (RemoteException e) { + // ignore + } + }, () -> { + synchronized (mLock) { + sendContinueUserSwitchLU(uss, oldUserId, newUserId); + } + }, "onUserSwitching"); + t.traceEnd(); + } + void informUserSwitchObservers(BiConsumer<IUserSwitchObserver, IRemoteCallback> consumer, + final Runnable onComplete, String trace) { final int observerCount = mUserSwitchObservers.beginBroadcast(); - if (observerCount > 0) { - final ArraySet<String> curWaitingUserSwitchCallbacks = new ArraySet<>(); + if (observerCount == 0) { + onComplete.run(); + mUserSwitchObservers.finishBroadcast(); + return; + } + final ArraySet<String> curWaitingUserSwitchCallbacks = new ArraySet<>(); + synchronized (mLock) { + mCurWaitingUserSwitchCallbacks = curWaitingUserSwitchCallbacks; + } + final AtomicInteger waitingCallbacksCount = new AtomicInteger(observerCount); + final long userSwitchTimeoutMs = getUserSwitchTimeoutMs(); + final long dispatchStartedTime = SystemClock.elapsedRealtime(); + for (int i = 0; i < observerCount; i++) { + final long dispatchStartedTimeForObserver = SystemClock.elapsedRealtime(); + // Prepend with unique prefix to guarantee that keys are unique + final String name = "#" + i + " " + mUserSwitchObservers.getBroadcastCookie(i); synchronized (mLock) { - uss.switching = true; - mCurWaitingUserSwitchCallbacks = curWaitingUserSwitchCallbacks; - } - final AtomicInteger waitingCallbacksCount = new AtomicInteger(observerCount); - final long userSwitchTimeoutMs = getUserSwitchTimeoutMs(); - final long dispatchStartedTime = SystemClock.elapsedRealtime(); - for (int i = 0; i < observerCount; i++) { - final long dispatchStartedTimeForObserver = SystemClock.elapsedRealtime(); - try { - // Prepend with unique prefix to guarantee that keys are unique - final String name = "#" + i + " " + mUserSwitchObservers.getBroadcastCookie(i); + curWaitingUserSwitchCallbacks.add(name); + } + final IRemoteCallback callback = new IRemoteCallback.Stub() { + @Override + public void sendResult(Bundle data) throws RemoteException { + asyncTraceEnd(trace + "-" + name, 0); synchronized (mLock) { - curWaitingUserSwitchCallbacks.add(name); - } - final IRemoteCallback callback = new IRemoteCallback.Stub() { - @Override - public void sendResult(Bundle data) throws RemoteException { - asyncTraceEnd("onUserSwitching-" + name, newUserId); - synchronized (mLock) { - long delayForObserver = SystemClock.elapsedRealtime() - - dispatchStartedTimeForObserver; - if (delayForObserver > LONG_USER_SWITCH_OBSERVER_WARNING_TIME_MS) { - Slogf.w(TAG, "User switch slowed down by observer " + name - + ": result took " + delayForObserver - + " ms to process."); - } - - long totalDelay = SystemClock.elapsedRealtime() - - dispatchStartedTime; - if (totalDelay > userSwitchTimeoutMs) { - Slogf.e(TAG, "User switch timeout: observer " + name - + "'s result was received " + totalDelay - + " ms after dispatchUserSwitch."); - } - - curWaitingUserSwitchCallbacks.remove(name); - // Continue switching if all callbacks have been notified and - // user switching session is still valid - if (waitingCallbacksCount.decrementAndGet() == 0 - && (curWaitingUserSwitchCallbacks - == mCurWaitingUserSwitchCallbacks)) { - sendContinueUserSwitchLU(uss, oldUserId, newUserId); - } - } + long delayForObserver = SystemClock.elapsedRealtime() + - dispatchStartedTimeForObserver; + if (delayForObserver > LONG_USER_SWITCH_OBSERVER_WARNING_TIME_MS) { + Slogf.w(TAG, "User switch slowed down by observer " + name + + ": result took " + delayForObserver + + " ms to process. " + trace); } - }; - asyncTraceBegin("onUserSwitching-" + name, newUserId); - mUserSwitchObservers.getBroadcastItem(i).onUserSwitching(newUserId, callback); - } catch (RemoteException e) { - // Ignore + long totalDelay = SystemClock.elapsedRealtime() - dispatchStartedTime; + if (totalDelay > userSwitchTimeoutMs) { + Slogf.e(TAG, "User switch timeout: observer " + name + + "'s result was received " + totalDelay + + " ms after dispatchUserSwitch. " + trace); + } + curWaitingUserSwitchCallbacks.remove(name); + // Continue switching if all callbacks have been notified and + // user switching session is still valid + if (waitingCallbacksCount.decrementAndGet() == 0 + && (curWaitingUserSwitchCallbacks + == mCurWaitingUserSwitchCallbacks)) { + onComplete.run(); + } + } } - } - } else { - synchronized (mLock) { - sendContinueUserSwitchLU(uss, oldUserId, newUserId); - } + }; + asyncTraceBegin(trace + "-" + name, 0); + consumer.accept(mUserSwitchObservers.getBroadcastItem(i), callback); } mUserSwitchObservers.finishBroadcast(); - t.traceEnd(); // end dispatchUserSwitch- } @GuardedBy("mLock") diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index 89e4a8d82676..cfcede8ee40d 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -288,3 +288,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "expedite_activity_launch_on_cold_start" + namespace: "system_performance" + description: "Notify ActivityTaskManager of cold starts early to fix app launch behavior." + bug: "319519089" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index bad5b8b9567a..5740e16dc886 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -2397,9 +2397,13 @@ public final class DisplayManagerService extends SystemService { // We don't bother invalidating the display info caches here because any changes to the // display info will trigger a cache invalidation inside of LogicalDisplay before we hit // this point. - sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); + sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED); applyDisplayChangedLocked(display); + + if (mDisplayTopologyCoordinator != null) { + mDisplayTopologyCoordinator.onDisplayChanged(display.getDisplayInfoLocked()); + } } private void applyDisplayChangedLocked(@NonNull LogicalDisplay display) { @@ -2643,7 +2647,8 @@ public final class DisplayManagerService extends SystemService { private void updateCanHostTasksIfNeededLocked(LogicalDisplay display) { if (display.setCanHostTasksLocked(!mMirrorBuiltInDisplay)) { - sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); + sendDisplayEventIfEnabledLocked(display, + DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED); } } @@ -3474,7 +3479,7 @@ public final class DisplayManagerService extends SystemService { private void sendDisplayEventFrameRateOverrideLocked(int displayId) { Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE, - displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); + displayId, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED); mHandler.sendMessage(msg); } @@ -4061,7 +4066,7 @@ public final class DisplayManagerService extends SystemService { handleLogicalDisplayAddedLocked(display); break; - case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CHANGED: + case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_BASIC_CHANGED: handleLogicalDisplayChangedLocked(display); break; @@ -4286,8 +4291,9 @@ public final class DisplayManagerService extends SystemService { switch (event) { case DisplayManagerGlobal.EVENT_DISPLAY_ADDED: return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED) != 0; - case DisplayManagerGlobal.EVENT_DISPLAY_CHANGED: - return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED) != 0; + case DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED: + return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED) + != 0; case DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED: return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED) @@ -4542,7 +4548,8 @@ public final class DisplayManagerService extends SystemService { public void registerCallback(IDisplayManagerCallback callback) { registerCallbackWithEventMask(callback, DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED - | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED); } diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java index 5b78726cc421..461a9f3f2a0d 100644 --- a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java +++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java @@ -85,13 +85,26 @@ class DisplayTopologyCoordinator { } /** + * Update the topology with display changes. + * @param info The new display info + */ + void onDisplayChanged(DisplayInfo info) { + synchronized (mSyncRoot) { + if (mTopology.updateDisplay(info.displayId, getWidth(info), getHeight(info))) { + sendTopologyUpdateLocked(); + } + } + } + + /** * Remove a display from the topology. * @param displayId The logical display ID */ void onDisplayRemoved(int displayId) { synchronized (mSyncRoot) { - mTopology.removeDisplay(displayId); - sendTopologyUpdateLocked(); + if (mTopology.removeDisplay(displayId)) { + sendTopologyUpdateLocked(); + } } } diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index 79592a656409..006921572977 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -81,7 +81,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { public static final int LOGICAL_DISPLAY_EVENT_BASE = 0; public static final int LOGICAL_DISPLAY_EVENT_ADDED = 1 << 0; - public static final int LOGICAL_DISPLAY_EVENT_CHANGED = 1 << 1; + public static final int LOGICAL_DISPLAY_EVENT_BASIC_CHANGED = 1 << 1; public static final int LOGICAL_DISPLAY_EVENT_REMOVED = 1 << 2; public static final int LOGICAL_DISPLAY_EVENT_SWAPPED = 1 << 3; public static final int LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED = 1 << 4; @@ -172,9 +172,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { /** * Has an entry for every logical display that the rest of the system has been notified about. - * Any entry in here requires us to send a {@link LOGICAL_DISPLAY_EVENT_REMOVED} event when it - * is deleted or {@link LOGICAL_DISPLAY_EVENT_CHANGED} when it is changed. The values are any - * of the {@code UPDATE_STATE_*} constant types. + * The values are any of the {@code UPDATE_STATE_*} constant types. */ private final SparseIntArray mUpdatedLogicalDisplays = new SparseIntArray(); @@ -811,7 +809,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { final boolean isCurrentlyEnabled = display.isEnabledLocked(); int logicalDisplayEventMask = mLogicalDisplaysToUpdate .get(displayId, LOGICAL_DISPLAY_EVENT_BASE); - + boolean hasBasicInfoChanged = + !mTempDisplayInfo.equals(newDisplayInfo, /* compareRefreshRate */ false); // The display is no longer valid and needs to be removed. if (!display.isValidLocked()) { // Remove from group @@ -863,19 +862,28 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { int event = isCurrentlyEnabled ? LOGICAL_DISPLAY_EVENT_ADDED : LOGICAL_DISPLAY_EVENT_REMOVED; logicalDisplayEventMask |= event; - } else if (wasDirty || !mTempDisplayInfo.equals(newDisplayInfo)) { + } else if (wasDirty) { // If only the hdr/sdr ratio changed, then send just the event for that case if ((diff == DisplayDeviceInfo.DIFF_HDR_SDR_RATIO)) { logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED; } else { - logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_CHANGED; + logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_BASIC_CHANGED + | LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED + | LOGICAL_DISPLAY_EVENT_STATE_CHANGED; } + } else if (hasBasicInfoChanged + || mTempDisplayInfo.getRefreshRate() != newDisplayInfo.getRefreshRate()) { + // If only the hdr/sdr ratio changed, then send just the event for that case + if ((diff == DisplayDeviceInfo.DIFF_HDR_SDR_RATIO)) { + logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED; + } else { - if (mFlags.isDisplayListenerPerformanceImprovementsEnabled()) { + if (hasBasicInfoChanged) { + logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_BASIC_CHANGED; + } logicalDisplayEventMask |= updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo); } - // The display is involved in a display layout transition } else if (updateState == UPDATE_STATE_TRANSITION) { logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION; @@ -891,7 +899,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { // things like display cutouts. display.getNonOverrideDisplayInfoLocked(mTempDisplayInfo); if (!mTempNonOverrideDisplayInfo.equals(mTempDisplayInfo)) { - logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_CHANGED; + logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_BASIC_CHANGED + | LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED; } } mLogicalDisplaysToUpdate.put(displayId, logicalDisplayEventMask); @@ -930,7 +939,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { if (mFlags.isConnectedDisplayManagementEnabled()) { sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_DISCONNECTED); } - sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_CHANGED); + sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_BASIC_CHANGED); sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED); sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_STATE_CHANGED); sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED); @@ -962,7 +971,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { mask |= LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED; } - if (mTempDisplayInfo.state != newDisplayInfo.state) { + if (mFlags.isDisplayListenerPerformanceImprovementsEnabled() + && mTempDisplayInfo.state != newDisplayInfo.state) { mask |= LOGICAL_DISPLAY_EVENT_STATE_CHANGED; } return mask; @@ -1357,8 +1367,6 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { return "added"; case LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION: return "transition"; - case LOGICAL_DISPLAY_EVENT_CHANGED: - return "changed"; case LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED: return "framerate_override"; case LOGICAL_DISPLAY_EVENT_SWAPPED: @@ -1375,6 +1383,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { return "state_changed"; case LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED: return "refresh_rate_changed"; + case LOGICAL_DISPLAY_EVENT_BASIC_CHANGED: + return "basic_changed"; } return null; } diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyGnssAssistanceProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyGnssAssistanceProvider.java new file mode 100644 index 000000000000..6cab60c05b8e --- /dev/null +++ b/services/core/java/com/android/server/location/provider/proxy/ProxyGnssAssistanceProvider.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location.provider.proxy; + +import android.annotation.Nullable; +import android.content.Context; +import android.location.provider.GnssAssistanceProviderBase; +import android.location.provider.IGnssAssistanceCallback; +import android.location.provider.IGnssAssistanceProvider; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import com.android.server.servicewatcher.CurrentUserServiceSupplier; +import com.android.server.servicewatcher.ServiceWatcher; + +/** + * Proxy for IGnssAssitanceProvider implementations. + */ +public class ProxyGnssAssistanceProvider { + + private static final String TAG = "GnssAssistanceProxy"; + /** + * Creates and registers this proxy. If no suitable service is available for the proxy, returns + * null. + */ + @Nullable + public static ProxyGnssAssistanceProvider createAndRegister(Context context) { + ProxyGnssAssistanceProvider proxy = new ProxyGnssAssistanceProvider(context); + if (proxy.register()) { + return proxy; + } else { + return null; + } + } + + private final ServiceWatcher mServiceWatcher; + + private ProxyGnssAssistanceProvider(Context context) { + mServiceWatcher = + ServiceWatcher.create( + context, + TAG, + CurrentUserServiceSupplier.createFromConfig( + context, + GnssAssistanceProviderBase.ACTION_GNSS_ASSISTANCE_PROVIDER, + com.android.internal.R.bool.config_enableGnssAssistanceOverlay, + com.android.internal.R.string + .config_gnssAssistanceProviderPackageName), + /* serviceListener= */ null); + } + + private boolean register() { + boolean resolves = mServiceWatcher.checkServiceResolves(); + if (resolves) { + mServiceWatcher.register(); + } + return resolves; + } + + /** + * Request GNSS assistance. + */ + public void request(IGnssAssistanceCallback callback) { + mServiceWatcher.runOnBinder( + new ServiceWatcher.BinderOperation() { + @Override + public void run(IBinder binder) throws RemoteException { + IGnssAssistanceProvider.Stub.asInterface(binder).request(callback); + } + + @Override + public void onError(Throwable t) { + try { + Log.w(TAG, "Error on requesting GnssAssistance: " + t); + callback.onError(); + } catch (RemoteException e) { + // ignore + } + } + }); + } +} diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java index 58c8450d714d..0d6e502cf965 100644 --- a/services/core/java/com/android/server/media/MediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; +import android.media.MediaRoute2ProviderService.Reason; import android.media.MediaRouter2; import android.media.MediaRouter2Utils; import android.media.RouteDiscoveryPreference; @@ -123,6 +124,13 @@ abstract class MediaRoute2Provider { } } + /** Calls {@link Callback#onRequestFailed} with the given id and reason. */ + protected void notifyRequestFailed(long requestId, @Reason int reason) { + if (mCallback != null) { + mCallback.onRequestFailed(/* provider= */ this, requestId, reason); + } + } + void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo) { setProviderState(providerInfo); notifyProviderState(); @@ -171,11 +179,34 @@ abstract class MediaRoute2Provider { void onProviderStateChanged(@Nullable MediaRoute2Provider provider); void onSessionCreated(@NonNull MediaRoute2Provider provider, long requestId, @Nullable RoutingSessionInfo sessionInfo); - void onSessionUpdated(@NonNull MediaRoute2Provider provider, - @NonNull RoutingSessionInfo sessionInfo); + + /** + * Called when there's a session info change. + * + * <p>If the provided {@code sessionInfo} has a null {@link + * RoutingSessionInfo#getClientPackageName()}, that means that it's applicable to all + * packages. We call this type of routing session "global". This is typically used for + * system provided {@link RoutingSessionInfo}. However, some applications may be exempted + * from the global routing sessions, because their media is being routed using a session + * different from the global routing session. + * + * @param provider The provider that owns the session that changed. + * @param sessionInfo The new {@link RoutingSessionInfo}. + * @param packageNamesWithRoutingSessionOverrides The names of packages that are not + * affected by global session changes. This set may only be non-empty when the {@code + * sessionInfo} is for the global session, and therefore has no {@link + * RoutingSessionInfo#getClientPackageName()}. + */ + void onSessionUpdated( + @NonNull MediaRoute2Provider provider, + @NonNull RoutingSessionInfo sessionInfo, + Set<String> packageNamesWithRoutingSessionOverrides); + void onSessionReleased(@NonNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo); - void onRequestFailed(@NonNull MediaRoute2Provider provider, long requestId, int reason); + + void onRequestFailed( + @NonNull MediaRoute2Provider provider, long requestId, @Reason int reason); } /** diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java index f09be2c15ee0..d6f7d3bdd4a4 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java @@ -16,6 +16,7 @@ package com.android.server.media; +import static android.media.MediaRoute2ProviderService.REASON_REJECTED; import static android.media.MediaRoute2ProviderService.REQUEST_ID_NONE; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; @@ -31,6 +32,7 @@ import android.media.IMediaRoute2ProviderServiceCallback; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; import android.media.MediaRoute2ProviderService; +import android.media.MediaRoute2ProviderService.Reason; import android.media.RouteDiscoveryPreference; import android.media.RoutingSessionInfo; import android.os.Bundle; @@ -41,6 +43,7 @@ import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.Log; import android.util.LongSparseArray; import android.util.Slog; @@ -89,6 +92,12 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { mRequestIdToSessionCreationRequest; @GuardedBy("mLock") + private final Map<String, SystemMediaSessionCallback> mSystemSessionCallbacks; + + @GuardedBy("mLock") + private final LongSparseArray<SystemMediaSessionCallback> mRequestIdToSystemSessionRequest; + + @GuardedBy("mLock") private final Map<String, SessionCreationOrTransferRequest> mSessionOriginalIdToTransferRequest; MediaRoute2ProviderServiceProxy( @@ -102,6 +111,8 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { mContext = Objects.requireNonNull(context, "Context must not be null."); mRequestIdToSessionCreationRequest = new LongSparseArray<>(); mSessionOriginalIdToTransferRequest = new HashMap<>(); + mRequestIdToSystemSessionRequest = new LongSparseArray<>(); + mSystemSessionCallbacks = new ArrayMap<>(); mIsSelfScanOnlyProvider = isSelfScanOnlyProvider; mSupportsSystemMediaRouting = supportsSystemMediaRouting; mUserId = userId; @@ -236,6 +247,48 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { } } + /** + * Requests the creation of a system media routing session. + * + * @param requestId The id of the request. + * @param uid The uid of the package whose media to route, or {@link + * android.os.Process#INVALID_UID} if not applicable (for example, if all the system's media + * must be routed). + * @param packageName The package name to populate {@link + * RoutingSessionInfo#getClientPackageName()}. + * @param routeId The id of the route to be initially {@link + * RoutingSessionInfo#getSelectedRoutes()}. + * @param sessionHints An optional bundle with paramets. + * @param callback A {@link SystemMediaSessionCallback} to notify of session events. + * @see MediaRoute2ProviderService#onCreateSystemRoutingSession + */ + public void requestCreateSystemMediaSession( + long requestId, + int uid, + String packageName, + String routeId, + @Nullable Bundle sessionHints, + @NonNull SystemMediaSessionCallback callback) { + if (!Flags.enableMirroringInMediaRouter2()) { + throw new IllegalStateException( + "Unexpected call to requestCreateSystemMediaSession. Governing flag is" + + " disabled."); + } + if (mConnectionReady) { + boolean binderRequestSucceeded = + mActiveConnection.requestCreateSystemMediaSession( + requestId, uid, packageName, routeId, sessionHints); + if (!binderRequestSucceeded) { + // notify failure. + return; + } + updateBinding(); + synchronized (mLock) { + mRequestIdToSystemSessionRequest.put(requestId, callback); + } + } + } + public boolean hasComponentName(String packageName, String className) { return mComponentName.getPackageName().equals(packageName) && mComponentName.getClassName().equals(className); @@ -292,7 +345,14 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { mLastDiscoveryPreference != null && mLastDiscoveryPreference.shouldPerformActiveScan() && mSupportsSystemMediaRouting; + boolean bindDueToOngoingSystemMediaRoutingSessions = false; + if (Flags.enableMirroringInMediaRouter2()) { + synchronized (mLock) { + bindDueToOngoingSystemMediaRoutingSessions = !mSystemSessionCallbacks.isEmpty(); + } + } if (!getSessionInfos().isEmpty() + || bindDueToOngoingSystemMediaRoutingSessions || bindDueToManagerScan || bindDueToSystemMediaRoutingSupport) { return true; @@ -438,6 +498,14 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { String newSessionId = newSession.getId(); synchronized (mLock) { + var systemMediaSessionCallback = mRequestIdToSystemSessionRequest.get(requestId); + if (systemMediaSessionCallback != null) { + mRequestIdToSystemSessionRequest.remove(requestId); + mSystemSessionCallbacks.put(newSession.getOriginalId(), systemMediaSessionCallback); + systemMediaSessionCallback.onSessionUpdate(newSession); + return; + } + if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) { newSession = createSessionWithPopulatedTransferInitiationDataLocked( @@ -569,6 +637,12 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { boolean found = false; synchronized (mLock) { + var sessionCallback = mSystemSessionCallbacks.get(releasedSession.getOriginalId()); + if (sessionCallback != null) { + sessionCallback.onSessionReleased(); + return; + } + mSessionOriginalIdToTransferRequest.remove(releasedSession.getId()); for (RoutingSessionInfo session : mSessionInfos) { if (TextUtils.equals(session.getId(), releasedSession.getId())) { @@ -602,7 +676,11 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { private void dispatchSessionUpdated(RoutingSessionInfo session) { mHandler.sendMessage( - obtainMessage(mCallback::onSessionUpdated, this, session)); + obtainMessage( + mCallback::onSessionUpdated, + this, + session, + /* packageNamesWithRoutingSessionOverrides= */ Set.of())); } private void dispatchSessionReleased(RoutingSessionInfo session) { @@ -645,6 +723,19 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { for (RoutingSessionInfo sessionInfo : mSessionInfos) { mCallback.onSessionReleased(this, sessionInfo); } + if (Flags.enableMirroringInMediaRouter2()) { + for (var callback : mSystemSessionCallbacks.values()) { + callback.onSessionReleased(); + } + mSystemSessionCallbacks.clear(); + int requestsSize = mRequestIdToSystemSessionRequest.size(); + for (int i = 0; i < requestsSize; i++) { + var callback = mRequestIdToSystemSessionRequest.valueAt(i); + var requestId = mRequestIdToSystemSessionRequest.keyAt(i); + callback.onRequestFailed(requestId, REASON_REJECTED); + } + mSystemSessionCallbacks.clear(); + } mSessionInfos.clear(); mReleasingSessions.clear(); mRequestIdToSessionCreationRequest.clear(); @@ -673,6 +764,26 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { pendingTransferCount); } + /** + * Callback for events related to system media sessions. + * + * @see MediaRoute2ProviderService#onCreateSystemRoutingSession + */ + public interface SystemMediaSessionCallback { + + /** + * Called when the corresponding session's {@link RoutingSessionInfo}, or upon the creation + * of the given session info. + */ + void onSessionUpdate(@NonNull RoutingSessionInfo sessionInfo); + + /** Called when the request with the given id fails for the given reason. */ + void onRequestFailed(long requestId, @Reason int reason); + + /** Called when the corresponding session is released. */ + void onSessionReleased(); + } + // All methods in this class are called on the main thread. private final class ServiceConnectionImpl implements ServiceConnection { @@ -739,6 +850,28 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { } } + /** + * Sends a system media session creation request to the provider service, and returns + * whether the request transaction succeeded. + * + * <p>The transaction might fail, for example, if the recipient process has died. + */ + public boolean requestCreateSystemMediaSession( + long requestId, + int uid, + String packageName, + String routeId, + @Nullable Bundle sessionHints) { + try { + mService.requestCreateSystemMediaSession( + requestId, uid, packageName, routeId, sessionHints); + return true; + } catch (RemoteException ex) { + Slog.e(TAG, "requestCreateSystemMediaSession: Failed to deliver request."); + } + return false; + } + public void releaseSession(long requestId, String sessionId) { try { mService.releaseSession(requestId, sessionId); diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 58deffcbd4ba..5e6737a485af 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -846,33 +846,29 @@ class MediaRouter2ServiceImpl { try { synchronized (mLock) { UserRecord userRecord = getOrCreateUserRecordLocked(userId); - List<RoutingSessionInfo> sessionInfos; + SystemMediaRoute2Provider systemProvider = userRecord.mHandler.getSystemProvider(); if (hasSystemRoutingPermissions) { - if (setDeviceRouteSelected && !Flags.enableMirroringInMediaRouter2()) { + if (!Flags.enableMirroringInMediaRouter2() && setDeviceRouteSelected) { // Return a fake system session that shows the device route as selected and // available bluetooth routes as transferable. - return userRecord.mHandler.getSystemProvider() - .generateDeviceRouteSelectedSessionInfo(targetPackageName); + return systemProvider.generateDeviceRouteSelectedSessionInfo( + targetPackageName); } else { - sessionInfos = userRecord.mHandler.getSystemProvider().getSessionInfos(); - if (!sessionInfos.isEmpty()) { - // Return a copy of the current system session with no modification, - // except setting the client package name. - return new RoutingSessionInfo.Builder(sessionInfos.get(0)) - .setClientPackageName(targetPackageName) - .build(); + RoutingSessionInfo session = + systemProvider.getSessionForPackage(targetPackageName); + if (session != null) { + return session; } else { Slog.w(TAG, "System provider does not have any session info."); + return null; } } } else { - return new RoutingSessionInfo.Builder( - userRecord.mHandler.getSystemProvider().getDefaultSessionInfo()) + return new RoutingSessionInfo.Builder(systemProvider.getDefaultSessionInfo()) .setClientPackageName(targetPackageName) .build(); } } - return null; } finally { Binder.restoreCallingIdentity(token); } @@ -2638,10 +2634,17 @@ class MediaRouter2ServiceImpl { } @Override - public void onSessionUpdated(@NonNull MediaRoute2Provider provider, - @NonNull RoutingSessionInfo sessionInfo) { - sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionInfoChangedOnHandler, - this, provider, sessionInfo)); + public void onSessionUpdated( + @NonNull MediaRoute2Provider provider, + @NonNull RoutingSessionInfo sessionInfo, + Set<String> packageNamesWithRoutingSessionOverrides) { + sendMessage( + PooledLambda.obtainMessage( + UserHandler::onSessionInfoChangedOnHandler, + this, + provider, + sessionInfo, + packageNamesWithRoutingSessionOverrides)); } @Override @@ -3152,10 +3155,31 @@ class MediaRouter2ServiceImpl { toOriginalRequestId(uniqueRequestId), sessionInfo); } - private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider, - @NonNull RoutingSessionInfo sessionInfo) { + /** + * Implementation of {@link MediaRoute2Provider.Callback#onSessionUpdated}. + * + * <p>Must run on the thread that corresponds to this {@link UserHandler}. + */ + private void onSessionInfoChangedOnHandler( + @NonNull MediaRoute2Provider provider, + @NonNull RoutingSessionInfo sessionInfo, + Set<String> packageNamesWithRoutingSessionOverrides) { List<ManagerRecord> managers = getManagerRecords(); for (ManagerRecord manager : managers) { + if (Flags.enableMirroringInMediaRouter2()) { + String targetPackageName = manager.mTargetPackageName; + boolean skipDueToOverride = + targetPackageName != null + && packageNamesWithRoutingSessionOverrides.contains( + targetPackageName); + boolean sessionIsForTargetPackage = + TextUtils.isEmpty(sessionInfo.getClientPackageName()) // is global. + || TextUtils.equals( + targetPackageName, sessionInfo.getClientPackageName()); + if (skipDueToOverride || !sessionIsForTargetPackage) { + continue; + } + } manager.notifySessionUpdated(sessionInfo); } diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index b93846bf9ee7..60fced1e0c51 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -62,7 +62,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { static final String SYSTEM_SESSION_ID = "SYSTEM_SESSION"; private final AudioManager mAudioManager; - private final Handler mHandler; + protected final Handler mHandler; private final Context mContext; private final UserHandle mUser; @@ -116,7 +116,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { () -> { publishProviderState(); if (updateSessionInfosIfNeeded()) { - notifySessionInfoUpdated(); + notifyGlobalSessionInfoUpdated(); } }); @@ -129,7 +129,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { () -> { publishProviderState(); if (updateSessionInfosIfNeeded()) { - notifySessionInfoUpdated(); + notifyGlobalSessionInfoUpdated(); } })); } @@ -161,7 +161,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { public void setCallback(Callback callback) { super.setCallback(callback); notifyProviderState(); - notifySessionInfoUpdated(); + notifyGlobalSessionInfoUpdated(); } @Override @@ -296,7 +296,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses() && updateSessionInfosIfNeeded()) { - notifySessionInfoUpdated(); + notifyGlobalSessionInfoUpdated(); } } @@ -327,6 +327,23 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } /** + * Returns the {@link RoutingSessionInfo} that corresponds to the package with the given name. + */ + public RoutingSessionInfo getSessionForPackage(String targetPackageName) { + synchronized (mLock) { + if (!mSessionInfos.isEmpty()) { + // Return a copy of the current system session with no modification, + // except setting the client package name. + return new RoutingSessionInfo.Builder(mSessionInfos.get(0)) + .setClientPackageName(targetPackageName) + .build(); + } else { + return null; + } + } + } + + /** * Builds a system {@link RoutingSessionInfo} with the selected route set to the currently * selected <b>device</b> route (wired or built-in, but not bluetooth) and transferable routes * set to the currently available (connected) bluetooth routes. @@ -626,20 +643,21 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { notifyProviderState(); } - void notifySessionInfoUpdated() { + void notifyGlobalSessionInfoUpdated() { if (mCallback == null) { return; } RoutingSessionInfo sessionInfo; synchronized (mLock) { - sessionInfo = mSessionInfos.get(0); - if (sessionInfo == null) { + if (mSessionInfos.isEmpty()) { return; } + sessionInfo = mSessionInfos.get(0); } - mCallback.onSessionUpdated(this, sessionInfo); + mCallback.onSessionUpdated( + this, sessionInfo, /* packageNamesWithRoutingSessionOverrides= */ Set.of()); } @Override diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java index 7dc30ab66fd2..8931e3a1426e 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java @@ -18,23 +18,33 @@ package com.android.server.media; import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManager; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; import android.media.MediaRoute2ProviderService; +import android.media.MediaRoute2ProviderService.Reason; +import android.media.MediaRouter2Utils; import android.media.RoutingSessionInfo; +import android.os.Binder; import android.os.Looper; +import android.os.Process; import android.os.UserHandle; -import android.util.ArraySet; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.Log; +import android.util.LongSparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.server.media.MediaRoute2ProviderServiceProxy.SystemMediaSessionCallback; -import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.stream.Stream; /** @@ -48,11 +58,33 @@ import java.util.stream.Stream; private static final String ROUTE_ID_PREFIX_SYSTEM = "SYSTEM"; private static final String ROUTE_ID_SYSTEM_SEPARATOR = "."; + private final PackageManager mPackageManager; + @GuardedBy("mLock") private MediaRoute2ProviderInfo mLastSystemProviderInfo; @GuardedBy("mLock") - private final Map<String, ProviderProxyRecord> mProxyRecords = new HashMap<>(); + private final Map<String, ProviderProxyRecord> mProxyRecords = new ArrayMap<>(); + + /** + * Maps package names to corresponding sessions maintained by {@link MediaRoute2ProviderService + * provider services}. + */ + @GuardedBy("mLock") + private final Map<String, SystemMediaSessionRecord> mPackageNameToSessionRecord = + new ArrayMap<>(); + + /** + * Maps route {@link MediaRoute2Info#getOriginalId original ids} to the id of the {@link + * MediaRoute2ProviderService provider service} that manages the corresponding route. + */ + @GuardedBy("mLock") + private final Map<String, String> mOriginalRouteIdToProviderId = new ArrayMap<>(); + + /** Maps request ids to pending session creation callbacks. */ + @GuardedBy("mLock") + private final LongSparseArray<SystemMediaSessionCallbackImpl> mPendingSessionCreations = + new LongSparseArray<>(); private static final ComponentName COMPONENT_NAME = new ComponentName( @@ -69,6 +101,128 @@ import java.util.stream.Stream; private SystemMediaRoute2Provider2(Context context, UserHandle user, Looper looper) { super(context, COMPONENT_NAME, user, looper); + mPackageManager = context.getPackageManager(); + } + + @Override + public void transferToRoute( + long requestId, + @NonNull UserHandle clientUserHandle, + @NonNull String clientPackageName, + String sessionOriginalId, + String routeOriginalId, + int transferReason) { + synchronized (mLock) { + var targetProviderProxyId = mOriginalRouteIdToProviderId.get(routeOriginalId); + var targetProviderProxyRecord = mProxyRecords.get(targetProviderProxyId); + // Holds the target route, if it's managed by a provider service. Holds null otherwise. + var serviceTargetRoute = + targetProviderProxyRecord != null + ? targetProviderProxyRecord.getRouteByOriginalId(routeOriginalId) + : null; + var existingSessionRecord = mPackageNameToSessionRecord.get(clientPackageName); + if (existingSessionRecord != null) { + var existingSession = existingSessionRecord.mSourceSessionInfo; + if (targetProviderProxyId != null + && TextUtils.equals( + targetProviderProxyId, existingSession.getProviderId())) { + // The currently selected route and target route both belong to the same + // provider. We tell the provider to handle the transfer. + targetProviderProxyRecord.requestTransfer( + existingSession.getOriginalId(), serviceTargetRoute); + } else { + // The target route is handled by a provider other than the target one. We need + // to release the existing session. + var currentProxyRecord = existingSessionRecord.getProxyRecord(); + if (currentProxyRecord != null) { + currentProxyRecord.releaseSession( + requestId, existingSession.getOriginalId()); + existingSessionRecord.removeSelfFromSessionMap(); + } + } + } + + if (serviceTargetRoute != null) { + boolean isGlobalSession = TextUtils.isEmpty(clientPackageName); + int uid; + if (isGlobalSession) { + uid = Process.INVALID_UID; + } else { + uid = fetchUid(clientPackageName, clientUserHandle); + if (uid == Process.INVALID_UID) { + throw new IllegalArgumentException( + "Cannot resolve transfer for " + + clientPackageName + + " and " + + clientUserHandle); + } + } + var pendingCreationCallback = + new SystemMediaSessionCallbackImpl( + targetProviderProxyId, requestId, clientPackageName); + mPendingSessionCreations.put(requestId, pendingCreationCallback); + targetProviderProxyRecord.requestCreateSystemMediaSession( + requestId, + uid, + clientPackageName, + routeOriginalId, + pendingCreationCallback); + } else { + // The target route is not provided by any of the services. Assume it's a system + // provided route. + super.transferToRoute( + requestId, + clientUserHandle, + clientPackageName, + sessionOriginalId, + routeOriginalId, + transferReason); + } + } + } + + @Nullable + @Override + public RoutingSessionInfo getSessionForPackage(String packageName) { + synchronized (mLock) { + var systemSession = super.getSessionForPackage(packageName); + if (systemSession == null) { + return null; + } + var overridingSession = mPackageNameToSessionRecord.get(packageName); + if (overridingSession != null) { + var builder = + new RoutingSessionInfo.Builder(overridingSession.mTranslatedSessionInfo) + .setProviderId(mUniqueId) + .setSystemSession(true); + for (var systemRoute : mLastSystemProviderInfo.getRoutes()) { + builder.addTransferableRoute(systemRoute.getOriginalId()); + } + return builder.build(); + } else { + return systemSession; + } + } + } + + /** + * Returns the uid that corresponds to the given name and user handle, or {@link + * Process#INVALID_UID} if a uid couldn't be found. + */ + @SuppressLint("MissingPermission") + // We clear the calling identity before calling the package manager, and we are running on the + // system_server. + private int fetchUid(String clientPackageName, UserHandle clientUserHandle) { + final long token = Binder.clearCallingIdentity(); + try { + return mPackageManager.getApplicationInfoAsUser( + clientPackageName, /* flags= */ 0, clientUserHandle) + .uid; + } catch (PackageManager.NameNotFoundException e) { + return Process.INVALID_UID; + } finally { + Binder.restoreCallingIdentity(token); + } } @Override @@ -85,21 +239,21 @@ import java.util.stream.Stream; } else { mProxyRecords.put(serviceProxy.mUniqueId, proxyRecord); } - setProviderState(buildProviderInfo()); + updateProviderInfo(); } updateSessionInfo(); notifyProviderState(); - notifySessionInfoUpdated(); + notifyGlobalSessionInfoUpdated(); } @Override public void onSystemProviderRoutesChanged(MediaRoute2ProviderInfo providerInfo) { synchronized (mLock) { mLastSystemProviderInfo = providerInfo; - setProviderState(buildProviderInfo()); + updateProviderInfo(); } updateSessionInfo(); - notifySessionInfoUpdated(); + notifyGlobalSessionInfoUpdated(); } /** @@ -116,10 +270,13 @@ import java.util.stream.Stream; var builder = new RoutingSessionInfo.Builder(systemSessionInfo); mProxyRecords.values().stream() .flatMap(ProviderProxyRecord::getRoutesStream) - .map(MediaRoute2Info::getId) + .map(MediaRoute2Info::getOriginalId) .forEach(builder::addTransferableRoute); mSessionInfos.clear(); mSessionInfos.add(builder.build()); + for (var sessionRecords : mPackageNameToSessionRecord.values()) { + mSessionInfos.add(sessionRecords.mTranslatedSessionInfo); + } } } @@ -129,13 +286,84 @@ import java.util.stream.Stream; * provider services}. */ @GuardedBy("mLock") - private MediaRoute2ProviderInfo buildProviderInfo() { + private void updateProviderInfo() { MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder(mLastSystemProviderInfo); - mProxyRecords.values().stream() - .flatMap(ProviderProxyRecord::getRoutesStream) - .forEach(builder::addRoute); - return builder.build(); + mOriginalRouteIdToProviderId.clear(); + for (var proxyRecord : mProxyRecords.values()) { + String proxyId = proxyRecord.mProxy.mUniqueId; + proxyRecord + .getRoutesStream() + .forEach( + route -> { + builder.addRoute(route); + mOriginalRouteIdToProviderId.put(route.getOriginalId(), proxyId); + }); + } + setProviderState(builder.build()); + } + + @Override + /* package */ void notifyGlobalSessionInfoUpdated() { + if (mCallback == null) { + return; + } + + RoutingSessionInfo sessionInfo; + Set<String> packageNamesWithRoutingSessionOverrides; + synchronized (mLock) { + if (mSessionInfos.isEmpty()) { + return; + } + packageNamesWithRoutingSessionOverrides = mPackageNameToSessionRecord.keySet(); + sessionInfo = mSessionInfos.getFirst(); + } + + mCallback.onSessionUpdated(this, sessionInfo, packageNamesWithRoutingSessionOverrides); + } + + private void onSessionOverrideUpdated(RoutingSessionInfo sessionInfo) { + // TODO: b/362507305 - Consider adding routes from other provider services. This is not a + // trivial change because a provider1-route to provider2-route transfer has seemingly two + // possible approachies. Either we first release the current session and then create the new + // one, in which case the audio is briefly going to leak through the system route. On the + // other hand, if we first create the provider2 session, then there will be a period during + // which there will be two overlapping routing policies asking for the exact same media + // stream. + var builder = new RoutingSessionInfo.Builder(sessionInfo); + mLastSystemProviderInfo.getRoutes().stream() + .map(MediaRoute2Info::getOriginalId) + .forEach(builder::addTransferableRoute); + mCallback.onSessionUpdated( + /* provider= */ this, + builder.build(), + /* packageNamesWithRoutingSessionOverrides= */ Set.of()); + } + + /** + * Equivalent to {@link #asSystemRouteId}, except it takes a unique route id instead of a + * original id. + */ + private static String uniqueIdAsSystemRouteId(String providerId, String uniqueRouteId) { + return asSystemRouteId(providerId, MediaRouter2Utils.getOriginalId(uniqueRouteId)); + } + + /** + * Returns a unique {@link MediaRoute2Info#getOriginalId() original id} for this provider to + * publish system media routes from {@link MediaRoute2ProviderService provider services}. + * + * <p>This provider will publish system media routes as part of the system routing session. + * However, said routes may also support {@link MediaRoute2Info#FLAG_ROUTING_TYPE_REMOTE remote + * routing}, meaning we cannot use the same id, or there would be an id collision. As a result, + * we derive a {@link MediaRoute2Info#getOriginalId original id} that is unique among all + * original route ids used by this provider. + */ + private static String asSystemRouteId(String providerId, String originalRouteId) { + return ROUTE_ID_PREFIX_SYSTEM + + ROUTE_ID_SYSTEM_SEPARATOR + + providerId + + ROUTE_ID_SYSTEM_SEPARATOR + + originalRouteId; } /** @@ -145,14 +373,69 @@ import java.util.stream.Stream; * @param mProxy The corresponding {@link MediaRoute2ProviderServiceProxy}. * @param mSystemMediaRoutes The last snapshot of routes from the service that support system * media routing, as defined by {@link MediaRoute2Info#supportsSystemMediaRouting()}. + * @param mNewOriginalIdToSourceOriginalIdMap Maps the {@link #mSystemMediaRoutes} ids to the + * original ids of corresponding {@link MediaRoute2ProviderService service} route. */ private record ProviderProxyRecord( MediaRoute2ProviderServiceProxy mProxy, - Collection<MediaRoute2Info> mSystemMediaRoutes) { + Map<String, MediaRoute2Info> mSystemMediaRoutes, + Map<String, String> mNewOriginalIdToSourceOriginalIdMap) { /** Returns a stream representation of the {@link #mSystemMediaRoutes}. */ public Stream<MediaRoute2Info> getRoutesStream() { - return mSystemMediaRoutes.stream(); + return mSystemMediaRoutes.values().stream(); + } + + @Nullable + public MediaRoute2Info getRouteByOriginalId(String routeOriginalId) { + return mSystemMediaRoutes.get(routeOriginalId); + } + + /** + * Requests the creation of a system media routing session. + * + * @param requestId The request id. + * @param uid The uid of the package whose media to route, or {@link Process#INVALID_UID} if + * not applicable. + * @param packageName The name of the package whose media to route. + * @param originalRouteId The {@link MediaRoute2Info#getOriginalId() original route id} of + * the route that should be initially selected. + * @param callback A {@link MediaRoute2ProviderServiceProxy.SystemMediaSessionCallback} for + * events. + * @see MediaRoute2ProviderService#onCreateSystemRoutingSession + */ + public void requestCreateSystemMediaSession( + long requestId, + int uid, + String packageName, + String originalRouteId, + SystemMediaSessionCallback callback) { + var targetRouteId = mNewOriginalIdToSourceOriginalIdMap.get(originalRouteId); + if (targetRouteId == null) { + Log.w( + TAG, + "Failed system media session creation due to lack of mapping for id: " + + originalRouteId); + callback.onRequestFailed( + requestId, MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE); + } else { + mProxy.requestCreateSystemMediaSession( + requestId, + uid, + packageName, + targetRouteId, + /* sessionHints= */ null, + callback); + } + } + + public void requestTransfer(String sessionId, MediaRoute2Info targetRoute) { + // TODO: Map the target route to the source route original id. + throw new UnsupportedOperationException("TODO Implement"); + } + + public void releaseSession(long requestId, String originalSessionId) { + mProxy.releaseSession(requestId, originalSessionId); } /** @@ -165,22 +448,177 @@ import java.util.stream.Stream; if (providerInfo == null) { return null; } - ArraySet<MediaRoute2Info> routes = new ArraySet<>(); - providerInfo.getRoutes().stream() - .filter(MediaRoute2Info::supportsSystemMediaRouting) - .forEach( - route -> { - String id = - ROUTE_ID_PREFIX_SYSTEM - + route.getProviderId() - + ROUTE_ID_SYSTEM_SEPARATOR - + route.getOriginalId(); - routes.add( - new MediaRoute2Info.Builder(id, route.getName()) - .addFeature(FEATURE_LIVE_AUDIO) - .build()); - }); - return new ProviderProxyRecord(serviceProxy, Collections.unmodifiableSet(routes)); + Map<String, MediaRoute2Info> routesMap = new ArrayMap<>(); + Map<String, String> idMap = new ArrayMap<>(); + for (MediaRoute2Info sourceRoute : providerInfo.getRoutes()) { + if (!sourceRoute.supportsSystemMediaRouting()) { + continue; + } + String id = + asSystemRouteId(providerInfo.getUniqueId(), sourceRoute.getOriginalId()); + var newRoute = + new MediaRoute2Info.Builder(id, sourceRoute.getName()) + .addFeature(FEATURE_LIVE_AUDIO) + .build(); + routesMap.put(id, newRoute); + idMap.put(id, sourceRoute.getOriginalId()); + } + return new ProviderProxyRecord( + serviceProxy, + Collections.unmodifiableMap(routesMap), + Collections.unmodifiableMap(idMap)); + } + } + + private class SystemMediaSessionCallbackImpl implements SystemMediaSessionCallback { + + private final String mProviderId; + private final long mRequestId; + private final String mClientPackageName; + // Accessed only on mHandler. + @Nullable private SystemMediaSessionRecord mSessionRecord; + + private SystemMediaSessionCallbackImpl( + String providerId, long requestId, String clientPackageName) { + mProviderId = providerId; + mRequestId = requestId; + mClientPackageName = clientPackageName; + } + + @Override + public void onSessionUpdate(@NonNull RoutingSessionInfo sessionInfo) { + mHandler.post( + () -> { + if (mSessionRecord != null) { + mSessionRecord.onSessionUpdate(sessionInfo); + } + SystemMediaSessionRecord systemMediaSessionRecord = + new SystemMediaSessionRecord(mProviderId, sessionInfo); + RoutingSessionInfo translatedSession; + synchronized (mLock) { + mSessionRecord = systemMediaSessionRecord; + mPackageNameToSessionRecord.put( + mClientPackageName, systemMediaSessionRecord); + mPendingSessionCreations.remove(mRequestId); + translatedSession = systemMediaSessionRecord.mTranslatedSessionInfo; + } + onSessionOverrideUpdated(translatedSession); + }); + } + + @Override + public void onRequestFailed(long requestId, @Reason int reason) { + mHandler.post( + () -> { + if (mSessionRecord != null) { + mSessionRecord.onRequestFailed(requestId, reason); + } + synchronized (mLock) { + mPendingSessionCreations.remove(mRequestId); + } + notifyRequestFailed(requestId, reason); + }); + } + + @Override + public void onSessionReleased() { + mHandler.post( + () -> { + if (mSessionRecord != null) { + mSessionRecord.onSessionReleased(); + } else { + // Should never happen. The session hasn't yet been created. + throw new IllegalStateException(); + } + }); + } + } + + private class SystemMediaSessionRecord implements SystemMediaSessionCallback { + + private final String mProviderId; + + @GuardedBy("SystemMediaRoute2Provider2.this.mLock") + @NonNull + private RoutingSessionInfo mSourceSessionInfo; + + /** + * The same as {@link #mSourceSessionInfo}, except ids are {@link #asSystemRouteId system + * provider ids}. + */ + @GuardedBy("SystemMediaRoute2Provider2.this.mLock") + @NonNull + private RoutingSessionInfo mTranslatedSessionInfo; + + SystemMediaSessionRecord( + @NonNull String providerId, @NonNull RoutingSessionInfo sessionInfo) { + mProviderId = providerId; + mSourceSessionInfo = sessionInfo; + mTranslatedSessionInfo = asSystemProviderSession(sessionInfo); + } + + @Override + public void onSessionUpdate(@NonNull RoutingSessionInfo sessionInfo) { + RoutingSessionInfo translatedSessionInfo = mTranslatedSessionInfo; + synchronized (mLock) { + mSourceSessionInfo = sessionInfo; + mTranslatedSessionInfo = asSystemProviderSession(sessionInfo); + } + onSessionOverrideUpdated(translatedSessionInfo); + } + + @Override + public void onRequestFailed(long requestId, @Reason int reason) { + notifyRequestFailed(requestId, reason); + } + + @Override + public void onSessionReleased() { + synchronized (mLock) { + removeSelfFromSessionMap(); + } + notifyGlobalSessionInfoUpdated(); + } + + @GuardedBy("SystemMediaRoute2Provider2.this.mLock") + @Nullable + public ProviderProxyRecord getProxyRecord() { + ProviderProxyRecord provider = mProxyRecords.get(mProviderId); + if (provider == null) { + // Unexpected condition where the proxy is no longer available while there's an + // ongoing session. Could happen due to a crash in the provider process. + removeSelfFromSessionMap(); + } + return provider; + } + + @GuardedBy("SystemMediaRoute2Provider2.this.mLock") + private void removeSelfFromSessionMap() { + mPackageNameToSessionRecord.remove(mSourceSessionInfo.getClientPackageName()); + } + + private RoutingSessionInfo asSystemProviderSession(RoutingSessionInfo session) { + var builder = + new RoutingSessionInfo.Builder(session) + .setProviderId(mUniqueId) + .setSystemSession(true) + .clearSelectedRoutes() + .clearSelectableRoutes() + .clearDeselectableRoutes() + .clearTransferableRoutes(); + session.getSelectedRoutes().stream() + .map(it -> uniqueIdAsSystemRouteId(session.getProviderId(), it)) + .forEach(builder::addSelectedRoute); + session.getSelectableRoutes().stream() + .map(it -> uniqueIdAsSystemRouteId(session.getProviderId(), it)) + .forEach(builder::addSelectableRoute); + session.getDeselectableRoutes().stream() + .map(it -> uniqueIdAsSystemRouteId(session.getProviderId(), it)) + .forEach(builder::addDeselectableRoute); + session.getTransferableRoutes().stream() + .map(it -> uniqueIdAsSystemRouteId(session.getProviderId(), it)) + .forEach(builder::addTransferableRoute); + return builder.build(); } } } diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java index 89902f7f8321..7cbbe2938fd5 100644 --- a/services/core/java/com/android/server/notification/NotificationDelegate.java +++ b/services/core/java/com/android/server/notification/NotificationDelegate.java @@ -101,4 +101,10 @@ public interface NotificationDelegate { void onNotificationFeedbackReceived(String key, Bundle feedback); void prepareForPossibleShutdown(); + + /** + * Called when the notification should be unbundled. + * @param key the notification key + */ + void unbundleNotification(String key); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index c6d7fc7508da..7375a68c547b 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -112,6 +112,7 @@ import static android.service.notification.Flags.FLAG_NOTIFICATION_CONVERSATION_ import static android.service.notification.Flags.callstyleCallbackApi; import static android.service.notification.Flags.notificationClassification; import static android.service.notification.Flags.notificationForceGrouping; +import static android.service.notification.Flags.notificationRegroupOnClassification; import static android.service.notification.Flags.redactSensitiveNotificationsBigTextStyle; import static android.service.notification.Flags.redactSensitiveNotificationsFromUntrustedListeners; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; @@ -1851,6 +1852,42 @@ public class NotificationManagerService extends SystemService { } } + @Override + public void unbundleNotification(String key) { + if (!(notificationClassification() && notificationRegroupOnClassification())) { + return; + } + synchronized (mNotificationLock) { + NotificationRecord r = mNotificationsByKey.get(key); + if (r == null) { + return; + } + + if (DBG) { + Slog.v(TAG, "unbundleNotification: " + r); + } + + boolean hasOriginalSummary = false; + if (r.getSbn().isAppGroup() && r.getNotification().isGroupChild()) { + final String oldGroupKey = GroupHelper.getFullAggregateGroupKey( + r.getSbn().getPackageName(), r.getOriginalGroupKey(), r.getUserId()); + NotificationRecord groupSummary = mSummaryByGroupKey.get(oldGroupKey); + // We only care about app-provided valid groups + hasOriginalSummary = (groupSummary != null + && !GroupHelper.isAggregatedGroup(groupSummary)); + } + + // Only NotificationRecord's mChannel is updated when bundled, the Notification + // mChannelId will always be the original channel. + String origChannelId = r.getNotification().getChannelId(); + NotificationChannel originalChannel = mPreferencesHelper.getNotificationChannel( + r.getSbn().getPackageName(), r.getUid(), origChannelId, false); + if (originalChannel != null && !origChannelId.equals(r.getChannel().getId())) { + r.updateNotificationChannel(originalChannel); + mGroupHelper.onNotificationUnbundled(r, hasOriginalSummary); + } + } + } }; NotificationManagerPrivate mNotificationManagerPrivate = new NotificationManagerPrivate() { diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 749952e3336f..15377d6b269a 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -559,7 +559,7 @@ public class PreferencesHelper implements RankingConfig { if (r.uid == UNKNOWN_UID) { if (Flags.persistIncompleteRestoreData()) { - r.userId = userId; + r.userIdWhenUidUnknown = userId; } mRestoredWithoutUids.put(unrestoredPackageKey(pkg, userId), r); } else { @@ -756,7 +756,7 @@ public class PreferencesHelper implements RankingConfig { if (Flags.persistIncompleteRestoreData() && r.uid == UNKNOWN_UID) { out.attributeLong(null, ATT_CREATION_TIME, r.creationTime); - out.attributeInt(null, ATT_USERID, r.userId); + out.attributeInt(null, ATT_USERID, r.userIdWhenUidUnknown); } if (!forBackup) { @@ -1959,7 +1959,7 @@ public class PreferencesHelper implements RankingConfig { ArrayList<ZenBypassingApp> bypassing = new ArrayList<>(); synchronized (mLock) { for (PackagePreferences p : mPackagePreferences.values()) { - if (p.userId != userId) { + if (UserHandle.getUserId(p.uid) != userId) { continue; } int totalChannelCount = p.channels.size(); @@ -3189,7 +3189,7 @@ public class PreferencesHelper implements RankingConfig { // Until we enable the UI, we should return false. boolean canHavePromotedNotifs = android.app.Flags.uiRichOngoing(); - @UserIdInt int userId; + @UserIdInt int userIdWhenUidUnknown; Delegate delegate = null; ArrayMap<String, NotificationChannel> channels = new ArrayMap<>(); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index c4d1cc723804..ec0f25169d75 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -4068,7 +4068,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Nullable IBinder focusedToken) { boolean handled = PhoneWindowManager.this.handleKeyGestureEvent(event, focusedToken); - if (handled && Arrays.stream(event.getKeycodes()).anyMatch( + if (handled && !event.isCancelled() && Arrays.stream(event.getKeycodes()).anyMatch( (keycode) -> keycode == KeyEvent.KEYCODE_POWER)) { mPowerKeyHandled = true; } diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index 0c3c46c75eee..7f88e7463208 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -479,6 +479,7 @@ public class Notifier { case PowerManager.PARTIAL_WAKE_LOCK: return BatteryStats.WAKE_TYPE_PARTIAL; + case PowerManager.FULL_WAKE_LOCK: case PowerManager.SCREEN_DIM_WAKE_LOCK: case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: return BatteryStats.WAKE_TYPE_FULL; @@ -503,6 +504,31 @@ public class Notifier { } } + @VisibleForTesting + int getWakelockMonitorTypeForLogging(int flags) { + switch (flags & PowerManager.WAKE_LOCK_LEVEL_MASK) { + case PowerManager.FULL_WAKE_LOCK, PowerManager.SCREEN_DIM_WAKE_LOCK, + PowerManager.SCREEN_BRIGHT_WAKE_LOCK: + return PowerManager.FULL_WAKE_LOCK; + case PowerManager.DRAW_WAKE_LOCK: + return PowerManager.DRAW_WAKE_LOCK; + case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK: + if (mSuspendWhenScreenOffDueToProximityConfig) { + return -1; + } + return PowerManager.PARTIAL_WAKE_LOCK; + case PowerManager.PARTIAL_WAKE_LOCK: + return PowerManager.PARTIAL_WAKE_LOCK; + case PowerManager.DOZE_WAKE_LOCK: + // Doze wake locks are an internal implementation detail of the + // communication between dream manager service and power manager + // service. They have no additive battery impact. + return -1; + default: + return -1; + } + } + /** * Notifies that the device is changing wakefulness. * This function may be called even if the previous change hasn't finished in @@ -1288,7 +1314,7 @@ public class Notifier { if (mBatteryStatsInternal == null) { return; } - final int type = flags & PowerManager.WAKE_LOCK_LEVEL_MASK; + final int type = getWakelockMonitorTypeForLogging(flags); if (workSource == null || workSource.isEmpty()) { final int mappedUid = mBatteryStatsInternal.getOwnerUid(ownerUid); mFrameworkStatsLogger.wakelockStateChanged(mappedUid, tag, type, eventType); diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java index aae7417970eb..83461125b404 100644 --- a/services/core/java/com/android/server/power/hint/HintManagerService.java +++ b/services/core/java/com/android/server/power/hint/HintManagerService.java @@ -20,6 +20,7 @@ import static android.os.Flags.adpfUseFmqChannel; import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import static com.android.server.power.hint.Flags.adpfSessionTag; +import static com.android.server.power.hint.Flags.cpuHeadroomAffinityCheck; import static com.android.server.power.hint.Flags.powerhintThreadCleanup; import static com.android.server.power.hint.Flags.resetOnForkEnabled; @@ -191,7 +192,7 @@ public final class HintManagerService extends SystemService { private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager"; private static final String PROPERTY_USE_HAL_HEADROOMS = "persist.hms.use_hal_headrooms"; private static final String PROPERTY_CHECK_HEADROOM_TID = "persist.hms.check_headroom_tid"; - + private static final String PROPERTY_CHECK_HEADROOM_AFFINITY = "persist.hms.check_affinity"; private Boolean mFMQUsesIntegratedEventFlag = false; private final Object mCpuHeadroomLock = new Object(); @@ -1501,6 +1502,10 @@ public final class HintManagerService extends SystemService { } } } + if (cpuHeadroomAffinityCheck() && params.tids.length > 1 + && SystemProperties.getBoolean(PROPERTY_CHECK_HEADROOM_AFFINITY, true)) { + checkThreadAffinityForTids(params.tids); + } halParams.tids = params.tids; } if (halParams.calculationWindowMillis @@ -1529,6 +1534,27 @@ public final class HintManagerService extends SystemService { return null; } } + private void checkThreadAffinityForTids(int[] tids) { + long[] reference = null; + for (int tid : tids) { + long[] affinity; + try { + affinity = Process.getSchedAffinity(tid); + } catch (Exception e) { + Slog.e(TAG, "Failed to get affinity " + tid, e); + throw new IllegalStateException("Could not check affinity for tid " + tid); + } + if (reference == null) { + reference = affinity; + } else if (!Arrays.equals(reference, affinity)) { + Slog.d(TAG, "Thread affinity is different: tid " + + tids[0] + "->" + Arrays.toString(reference) + ", tid " + + tid + "->" + Arrays.toString(affinity)); + throw new IllegalStateException("Thread affinity is not the same for tids " + + Arrays.toString(tids)); + } + } + } private void checkCpuHeadroomParams(CpuHeadroomParamsInternal params) { boolean calculationTypeMatched = false; diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index f8877ad912b5..c18918fd9f8b 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -2175,6 +2175,19 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } } + /** + * Called when the notification should be unbundled. + * @param key the notification key + */ + @Override + public void unbundleNotification(@Nullable String key) { + enforceStatusBarService(); + enforceValidCallingUser(); + Binder.withCleanCallingIdentity(() -> { + mNotificationDelegate.unbundleNotification(key); + }); + } + @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, diff --git a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java index dad3a784cfb9..bb163ef4c1d5 100644 --- a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java +++ b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java @@ -369,10 +369,9 @@ public class CacheQuotaStrategy implements RemoteCallback.OnResultListener { tagName = parser.getName(); if (TAG_QUOTA.equals(tagName)) { CacheQuotaHint request = getRequestFromXml(parser); - if (request == null) { - continue; + if (request != null) { + quotas.add(request); } - quotas.add(request); } } eventType = parser.next(); diff --git a/services/core/java/com/android/server/updates/Android.bp b/services/core/java/com/android/server/updates/Android.bp deleted file mode 100644 index 10beebb82711..000000000000 --- a/services/core/java/com/android/server/updates/Android.bp +++ /dev/null @@ -1,11 +0,0 @@ -aconfig_declarations { - name: "updates_flags", - package: "com.android.server.updates", - container: "system", - srcs: ["*.aconfig"], -} - -java_aconfig_library { - name: "updates_flags_lib", - aconfig_declarations: "updates_flags", -} diff --git a/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java b/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java deleted file mode 100644 index af4025e1db7c..000000000000 --- a/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2016 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.updates; - -import android.content.Context; -import android.content.Intent; -import android.os.FileUtils; -import android.system.ErrnoException; -import android.system.Os; -import android.util.Slog; - -import libcore.io.Streams; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileFilter; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; - -public class CertificateTransparencyLogInstallReceiver extends ConfigUpdateInstallReceiver { - - private static final String TAG = "CTLogInstallReceiver"; - private static final String LOGDIR_PREFIX = "logs-"; - - public CertificateTransparencyLogInstallReceiver() { - super("/data/misc/keychain/ct/", "ct_logs", "metadata/", "version"); - } - - @Override - protected void install(InputStream inputStream, int version) throws IOException { - if (!Flags.certificateTransparencyInstaller()) { - return; - } - // To support atomically replacing the old configuration directory with the new there's a - // bunch of steps. We create a new directory with the logs and then do an atomic update of - // the current symlink to point to the new directory. - // 1. Ensure that the update dir exists and is readable - updateDir.mkdir(); - if (!updateDir.isDirectory()) { - throw new IOException("Unable to make directory " + updateDir.getCanonicalPath()); - } - if (!updateDir.setReadable(true, false)) { - throw new IOException("Unable to set permissions on " + updateDir.getCanonicalPath()); - } - File currentSymlink = new File(updateDir, "current"); - File newVersion = new File(updateDir, LOGDIR_PREFIX + String.valueOf(version)); - // 2. Handle the corner case where the new directory already exists. - if (newVersion.exists()) { - // If the symlink has already been updated then the update died between steps 7 and 8 - // and so we cannot delete the directory since its in use. Instead just bump the version - // and return. - if (newVersion.getCanonicalPath().equals(currentSymlink.getCanonicalPath())) { - writeUpdate( - updateDir, - updateVersion, - new ByteArrayInputStream(Long.toString(version).getBytes())); - deleteOldLogDirectories(); - return; - } else { - FileUtils.deleteContentsAndDir(newVersion); - } - } - try { - // 3. Create /data/misc/keychain/ct/<new_version>/ . - newVersion.mkdir(); - if (!newVersion.isDirectory()) { - throw new IOException("Unable to make directory " + newVersion.getCanonicalPath()); - } - if (!newVersion.setReadable(true, false)) { - throw new IOException( - "Failed to set " + newVersion.getCanonicalPath() + " readable"); - } - - // 4. Validate the log list json and move the file in <new_version>/ . - installLogList(newVersion, inputStream); - - // 5. Create the temp symlink. We'll rename this to the target symlink to get an atomic - // update. - File tempSymlink = new File(updateDir, "new_symlink"); - try { - Os.symlink(newVersion.getCanonicalPath(), tempSymlink.getCanonicalPath()); - } catch (ErrnoException e) { - throw new IOException("Failed to create symlink", e); - } - - // 6. Update the symlink target, this is the actual update step. - tempSymlink.renameTo(currentSymlink.getAbsoluteFile()); - } catch (IOException | RuntimeException e) { - FileUtils.deleteContentsAndDir(newVersion); - throw e; - } - Slog.i(TAG, "CT log directory updated to " + newVersion.getAbsolutePath()); - // 7. Update the current version information - writeUpdate( - updateDir, - updateVersion, - new ByteArrayInputStream(Long.toString(version).getBytes())); - // 8. Cleanup - deleteOldLogDirectories(); - } - - @Override - protected void postInstall(Context context, Intent intent) { - if (!Flags.certificateTransparencyInstaller()) { - return; - } - } - - private void installLogList(File directory, InputStream inputStream) throws IOException { - try { - byte[] content = Streams.readFullyNoClose(inputStream); - if (new JSONObject(new String(content, StandardCharsets.UTF_8)).length() == 0) { - throw new IOException("Log list data not valid"); - } - - File file = new File(directory, "log_list.json"); - try (FileOutputStream outputStream = new FileOutputStream(file)) { - outputStream.write(content); - } - if (!file.setReadable(true, false)) { - throw new IOException("Failed to set permissions on " + file.getCanonicalPath()); - } - } catch (JSONException e) { - throw new IOException("Malformed json in log list", e); - } - } - - private void deleteOldLogDirectories() throws IOException { - if (!updateDir.exists()) { - return; - } - File currentTarget = new File(updateDir, "current").getCanonicalFile(); - FileFilter filter = - new FileFilter() { - @Override - public boolean accept(File file) { - return !currentTarget.equals(file) - && file.getName().startsWith(LOGDIR_PREFIX); - } - }; - for (File f : updateDir.listFiles(filter)) { - FileUtils.deleteContentsAndDir(f); - } - } -} diff --git a/services/core/java/com/android/server/updates/flags.aconfig b/services/core/java/com/android/server/updates/flags.aconfig deleted file mode 100644 index 476cb3723c97..000000000000 --- a/services/core/java/com/android/server/updates/flags.aconfig +++ /dev/null @@ -1,10 +0,0 @@ -package: "com.android.server.updates" -container: "system" - -flag { - name: "certificate_transparency_installer" - is_exported: true - namespace: "network_security" - description: "Enable certificate transparency installer for log list data" - bug: "319829948" -} diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java index 1a9d21187ddb..6709e3a72db0 100644 --- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java +++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java @@ -25,6 +25,9 @@ import static android.app.PendingIntent.FLAG_ONE_SHOT; import static android.app.admin.DevicePolicyManager.EXTRA_RESTRICTION; import static android.app.admin.DevicePolicyManager.POLICY_SUSPEND_PACKAGES; import static android.content.Context.KEYGUARD_SERVICE; +import static android.content.Intent.ACTION_MAIN; +import static android.content.Intent.CATEGORY_HOME; +import static android.content.Intent.CATEGORY_SECONDARY_HOME; import static android.content.Intent.EXTRA_INTENT; import static android.content.Intent.EXTRA_PACKAGE_NAME; import static android.content.Intent.EXTRA_TASK_ID; @@ -40,6 +43,7 @@ import android.app.ActivityOptions; import android.app.KeyguardManager; import android.app.TaskInfo; import android.app.admin.DevicePolicyManagerInternal; +import android.content.ComponentName; import android.content.Context; import android.content.IIntentSender; import android.content.Intent; @@ -67,6 +71,7 @@ import com.android.internal.app.UnlaunchableAppActivity; import com.android.server.LocalServices; import com.android.server.am.ActivityManagerService; import com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptResult; +import com.android.window.flags.Flags; /** * A class that contains activity intercepting logic for {@link ActivityStarter#execute()} @@ -119,6 +124,11 @@ class ActivityStartInterceptor { */ TaskDisplayArea mPresumableLaunchDisplayArea; + /** + * Whether the component is specified originally in the given Intent. + */ + boolean mComponentSpecified; + ActivityStartInterceptor( ActivityTaskManagerService service, ActivityTaskSupervisor supervisor) { this(service, supervisor, service.mContext); @@ -185,6 +195,14 @@ class ActivityStartInterceptor { return TaskFragment.fromTaskFragmentToken(taskFragToken, mService); } + // TODO: consolidate this method with the one below since this is used for test only. + boolean intercept(Intent intent, ResolveInfo rInfo, ActivityInfo aInfo, String resolvedType, + Task inTask, TaskFragment inTaskFragment, int callingPid, int callingUid, + ActivityOptions activityOptions, TaskDisplayArea presumableLaunchDisplayArea) { + return intercept(intent, rInfo, aInfo, resolvedType, inTask, inTaskFragment, callingPid, + callingUid, activityOptions, presumableLaunchDisplayArea, false); + } + /** * Intercept the launch intent based on various signals. If an interception happened the * internal variables get assigned and need to be read explicitly by the caller. @@ -193,7 +211,8 @@ class ActivityStartInterceptor { */ boolean intercept(Intent intent, ResolveInfo rInfo, ActivityInfo aInfo, String resolvedType, Task inTask, TaskFragment inTaskFragment, int callingPid, int callingUid, - ActivityOptions activityOptions, TaskDisplayArea presumableLaunchDisplayArea) { + ActivityOptions activityOptions, TaskDisplayArea presumableLaunchDisplayArea, + boolean componentSpecified) { mUserManager = UserManager.get(mServiceContext); mIntent = intent; @@ -206,6 +225,7 @@ class ActivityStartInterceptor { mInTaskFragment = inTaskFragment; mActivityOptions = activityOptions; mPresumableLaunchDisplayArea = presumableLaunchDisplayArea; + mComponentSpecified = componentSpecified; if (interceptQuietProfileIfNeeded()) { // If work profile is turned off, skip the work challenge since the profile can only @@ -230,7 +250,8 @@ class ActivityStartInterceptor { } if (interceptHomeIfNeeded()) { // Replace primary home intents directed at displays that do not support primary home - // but support secondary home with the relevant secondary home activity. + // but support secondary home with the relevant secondary home activity. Or the home + // intent is not in the correct format. return true; } @@ -479,9 +500,78 @@ class ActivityStartInterceptor { if (mPresumableLaunchDisplayArea == null || mService.mRootWindowContainer == null) { return false; } - if (!ActivityRecord.isHomeIntent(mIntent)) { - return false; + + boolean intercepted = false; + if (Flags.normalizeHomeIntent()) { + if (!ACTION_MAIN.equals(mIntent.getAction()) || (!mIntent.hasCategory(CATEGORY_HOME) + && !mIntent.hasCategory(CATEGORY_SECONDARY_HOME))) { + // not a home intent + return false; + } + + if (mComponentSpecified) { + final ComponentName homeComponent = mIntent.getComponent(); + final Intent homeIntent = mService.getHomeIntent(); + final ActivityInfo aInfo = mService.mRootWindowContainer.resolveHomeActivity( + mUserId, homeIntent); + if (!aInfo.getComponentName().equals(homeComponent)) { + // Do nothing if the intent is not for the default home component. + return false; + } + } + + if (!ActivityRecord.isHomeIntent(mIntent) || mComponentSpecified) { + // This is not a standard home intent, make it so if possible. + normalizeHomeIntent(); + intercepted = true; + } + } else { + if (!ActivityRecord.isHomeIntent(mIntent)) { + return false; + } + } + + intercepted |= replaceToSecondaryHomeIntentIfNeeded(); + if (intercepted) { + mCallingPid = mRealCallingPid; + mCallingUid = mRealCallingUid; + mResolvedType = null; + + mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, /* flags= */ 0, + mRealCallingUid, mRealCallingPid); + mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, /*profilerInfo=*/ + null); + } + return intercepted; + } + + private void normalizeHomeIntent() { + Slog.w(TAG, "The home Intent is not correctly formatted"); + if (mIntent.getCategories().size() > 1) { + Slog.d(TAG, "Purge home intent categories"); + boolean isSecondaryHome = false; + final Object[] categories = mIntent.getCategories().toArray(); + for (int i = categories.length - 1; i >= 0; i--) { + final String category = (String) categories[i]; + if (CATEGORY_SECONDARY_HOME.equals(category)) { + isSecondaryHome = true; + } + mIntent.removeCategory(category); + } + mIntent.addCategory(isSecondaryHome ? CATEGORY_SECONDARY_HOME : CATEGORY_HOME); + } + if (mIntent.getType() != null || mIntent.getData() != null) { + Slog.d(TAG, "Purge home intent data/type"); + mIntent.setType(null); } + if (mComponentSpecified) { + Slog.d(TAG, "Purge home intent component, " + mIntent.getComponent()); + mIntent.setComponent(null); + } + mIntent.addFlags(FLAG_ACTIVITY_NEW_TASK); + } + + private boolean replaceToSecondaryHomeIntentIfNeeded() { if (!mIntent.hasCategory(Intent.CATEGORY_HOME)) { // Already a secondary home intent, leave it alone. return false; @@ -506,13 +596,6 @@ class ActivityStartInterceptor { // and should not be moved to the caller's task. Also, activities cannot change their type, // e.g. a standard activity cannot become a home activity. mIntent.addFlags(FLAG_ACTIVITY_NEW_TASK); - mCallingPid = mRealCallingPid; - mCallingUid = mRealCallingUid; - mResolvedType = null; - - mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, /* flags= */ 0, - mRealCallingUid, mRealCallingPid); - mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, /*profilerInfo=*/ null); return true; } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index acb93844c945..0ab2ffe3e298 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1341,7 +1341,8 @@ class ActivityStarter { callingPackage, callingFeatureId); if (mInterceptor.intercept(intent, rInfo, aInfo, resolvedType, inTask, inTaskFragment, - callingPid, callingUid, checkedOptions, suggestedLaunchDisplayArea)) { + callingPid, callingUid, checkedOptions, suggestedLaunchDisplayArea, + request.componentSpecified)) { // activity start was intercepted, e.g. because the target user is currently in quiet // mode (turn off work) or the target application is suspended intent = mInterceptor.mIntent; diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java index 0369a0ff4c76..9f88bc952351 100644 --- a/services/core/java/com/android/server/wm/AppCompatUtils.java +++ b/services/core/java/com/android/server/wm/AppCompatUtils.java @@ -164,15 +164,13 @@ final class AppCompatUtils { appCompatTaskInfo.setIsFromLetterboxDoubleTap(reachabilityOverrides.isFromDoubleTap()); + appCompatTaskInfo.topActivityAppBounds.set(getAppBounds(top)); final boolean isTopActivityLetterboxed = top.areBoundsLetterboxed(); appCompatTaskInfo.setTopActivityLetterboxed(isTopActivityLetterboxed); if (isTopActivityLetterboxed) { final Rect bounds = top.getBounds(); - final Rect appBounds = getAppBounds(top); appCompatTaskInfo.topActivityLetterboxWidth = bounds.width(); appCompatTaskInfo.topActivityLetterboxHeight = bounds.height(); - appCompatTaskInfo.topActivityLetterboxAppWidth = appBounds.width(); - appCompatTaskInfo.topActivityLetterboxAppHeight = appBounds.height(); // TODO(b/379824541) Remove duplicate information. appCompatTaskInfo.topActivityLetterboxBounds = bounds; // We need to consider if letterboxed or pillarboxed. @@ -281,8 +279,7 @@ final class AppCompatUtils { info.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET; info.topActivityLetterboxWidth = TaskInfo.PROPERTY_VALUE_UNSET; info.topActivityLetterboxHeight = TaskInfo.PROPERTY_VALUE_UNSET; - info.topActivityLetterboxAppHeight = TaskInfo.PROPERTY_VALUE_UNSET; - info.topActivityLetterboxAppWidth = TaskInfo.PROPERTY_VALUE_UNSET; + info.topActivityAppBounds.setEmpty(); info.topActivityLetterboxBounds = null; info.cameraCompatTaskInfo.freeformCameraCompatMode = CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_UNSPECIFIED; diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java index dd1af0a497ca..6b6f0111305c 100644 --- a/services/core/java/com/android/server/wm/AsyncRotationController.java +++ b/services/core/java/com/android/server/wm/AsyncRotationController.java @@ -29,9 +29,6 @@ import android.view.SurfaceControl; import android.view.WindowManager; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; -import android.view.animation.AnimationUtils; - -import com.android.internal.R; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -687,11 +684,12 @@ class AsyncRotationController extends FadeAnimationController implements Consume @Override public Animation getFadeInAnimation() { + final Animation anim = super.getFadeInAnimation(); if (mHasScreenRotationAnimation) { // Use a shorter animation so it is easier to align with screen rotation animation. - return AnimationUtils.loadAnimation(mContext, R.anim.screen_rotate_0_enter); + anim.setDuration(getScaledDuration(SHORT_DURATION_MS)); } - return super.getFadeInAnimation(); + return anim; } @Override diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java index 8eccffd8fe3b..a4e58ef923b8 100644 --- a/services/core/java/com/android/server/wm/ContentRecorder.java +++ b/services/core/java/com/android/server/wm/ContentRecorder.java @@ -368,6 +368,15 @@ final class ContentRecorder implements WindowContainerListener { return; } + final SurfaceControl sourceSurface = mRecordedWindowContainer.getSurfaceControl(); + if (sourceSurface == null || !sourceSurface.isValid()) { + ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, + "Content Recording: Unable to start recording for display %d since the " + + "surface is null or have been released.", + mDisplayContent.getDisplayId()); + return; + } + final int contentToRecord = mContentRecordingSession.getContentToRecord(); // TODO(b/297514518) Do not start capture if the app is in PIP, the bounds are inaccurate. @@ -395,8 +404,7 @@ final class ContentRecorder implements WindowContainerListener { mDisplayContent.getDisplayId(), mDisplayContent.getDisplayInfo().state); // Create a mirrored hierarchy for the SurfaceControl of the DisplayArea to capture. - mRecordedSurface = SurfaceControl.mirrorSurface( - mRecordedWindowContainer.getSurfaceControl()); + mRecordedSurface = SurfaceControl.mirrorSurface(sourceSurface); SurfaceControl.Transaction transaction = mDisplayContent.mWmService.mTransactionFactory.get() // Set the mMirroredSurface's parent to the root SurfaceControl for this diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index f8086615b7d1..4dd950ba6ee9 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -2908,6 +2908,18 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp && !mDisplayRotation.isRotatingSeamlessly()) { clearFixedRotationLaunchingApp(); } + // If there won't be a transition to notify the launch is done, then it should be ready to + // update with display orientation. E.g. a translucent activity enters pip from a task which + // contains another opaque activity. + if (mFixedRotationLaunchingApp != null && mFixedRotationLaunchingApp.isVisible() + && !mTransitionController.isCollecting() + && !mAtmService.mBackNavigationController.isMonitoringFinishTransition()) { + final Transition finishTransition = mTransitionController.mFinishingTransition; + if (finishTransition == null || !finishTransition.mParticipants.contains( + mFixedRotationLaunchingApp)) { + continueUpdateOrientationForDiffOrienLaunchingApp(); + } + } } /** diff --git a/services/core/java/com/android/server/wm/FadeAnimationController.java b/services/core/java/com/android/server/wm/FadeAnimationController.java index 7af67e63f469..c60d3677319a 100644 --- a/services/core/java/com/android/server/wm/FadeAnimationController.java +++ b/services/core/java/com/android/server/wm/FadeAnimationController.java @@ -20,41 +20,51 @@ import static com.android.server.wm.AnimationSpecProto.WINDOW; import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION; import android.annotation.NonNull; -import android.content.Context; import android.util.proto.ProtoOutputStream; import android.view.SurfaceControl; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.AlphaAnimation; import android.view.animation.Animation; -import android.view.animation.AnimationUtils; +import android.view.animation.DecelerateInterpolator; import android.view.animation.Transformation; -import com.android.internal.R; - import java.io.PrintWriter; /** * An animation controller to fade-in/out for a window token. */ public class FadeAnimationController { + static final int SHORT_DURATION_MS = 200; + static final int MEDIUM_DURATION_MS = 350; + protected final DisplayContent mDisplayContent; - protected final Context mContext; public FadeAnimationController(DisplayContent displayContent) { mDisplayContent = displayContent; - mContext = displayContent.mWmService.mContext; } /** * @return a fade-in Animation. */ public Animation getFadeInAnimation() { - return AnimationUtils.loadAnimation(mContext, R.anim.fade_in); + final AlphaAnimation anim = new AlphaAnimation(0f, 1f); + anim.setDuration(getScaledDuration(MEDIUM_DURATION_MS)); + anim.setInterpolator(new DecelerateInterpolator()); + return anim; } /** * @return a fade-out Animation. */ public Animation getFadeOutAnimation() { - return AnimationUtils.loadAnimation(mContext, R.anim.fade_out); + final AlphaAnimation anim = new AlphaAnimation(1f, 0f); + anim.setDuration(getScaledDuration(SHORT_DURATION_MS)); + anim.setInterpolator(new AccelerateInterpolator()); + return anim; + } + + long getScaledDuration(int durationMs) { + return (long) (durationMs * mDisplayContent.mWmService.getWindowAnimationScaleLocked()); } /** Run the fade in/out animation for a window token. */ diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS index 98521d36ad44..dede7676a4b6 100644 --- a/services/core/java/com/android/server/wm/OWNERS +++ b/services/core/java/com/android/server/wm/OWNERS @@ -21,6 +21,7 @@ jiamingliu@google.com pdwilliams@google.com charlesccchen@google.com marziana@google.com +mcarli@google.com # Files related to background activity launches per-file Background*Start* = set noparent diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 7fdc2c67b5ce..44f000da3d73 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -1515,9 +1515,9 @@ class RecentTasks { boolean skipExcludedCheck) { if (!skipExcludedCheck) { // Keep the most recent task of home display even if it is excluded from recents. - final boolean isExcludeFromRecents = - (task.getBaseIntent().getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) - == FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; + final boolean isExcludeFromRecents = task.getBaseIntent() != null + && (task.getBaseIntent().getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + == FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; if (isExcludeFromRecents) { if (DEBUG_RECENTS_TRIM_TASKS) { Slog.d(TAG, diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 76d8861022bb..9062afb50acb 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3652,14 +3652,6 @@ class Task extends TaskFragment { // If the developer has persist a different configuration, we need to override it to the // starting window because persisted configuration does not effect to Task. info.taskInfo.configuration.setTo(activity.getConfiguration()); - if (!Flags.drawSnapshotAspectRatioMatch()) { - final WindowState mainWindow = getTopFullscreenMainWindow(); - if (mainWindow != null) { - info.topOpaqueWindowInsetsState = - mainWindow.getInsetsStateWithVisibilityOverride(); - info.topOpaqueWindowLayoutParams = mainWindow.getAttrs(); - } - } return info; } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 9d9c53dfe0f4..db62cebf7603 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2945,7 +2945,7 @@ public class WindowManagerService extends IWindowManager.Stub final DisplayContent dc = mRoot.getDisplayContent(displayId); if (dc == null) { if (callingPid != MY_PID) { - throw new WindowManager.InvalidDisplayException( + throw new IllegalArgumentException( "attachWindowContextToDisplayContent: trying to attach to a" + " non-existing display:" + displayId); } diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 01639cc3b516..d26539c377a9 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -80,6 +80,7 @@ cc_library_static { ":lib_oomConnection_native", ":lib_anrTimer_native", ":lib_lazilyRegisteredServices_native", + ":lib_phantomProcessList_native", ], include_dirs: [ @@ -265,3 +266,10 @@ filegroup { "com_android_server_vr_VrManagerService.cpp", ], } + +filegroup { + name: "lib_phantomProcessList_native", + srcs: [ + "com_android_server_am_PhantomProcessList.cpp", + ], +} diff --git a/services/core/jni/com_android_server_am_PhantomProcessList.cpp b/services/core/jni/com_android_server_am_PhantomProcessList.cpp new file mode 100644 index 000000000000..0c5e6d85919a --- /dev/null +++ b/services/core/jni/com_android_server_am_PhantomProcessList.cpp @@ -0,0 +1,49 @@ +/* + * 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. + */ + +#include <jni.h> +#include <nativehelper/JNIHelp.h> +#include <processgroup/processgroup.h> + +namespace android { +namespace { + +jstring getCgroupProcsPath(JNIEnv* env, jobject clazz, jint uid, jint pid) { + if (uid < 0) { + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "uid is negative: %d", uid); + return nullptr; + } + + std::string path; + if (!CgroupGetAttributePathForProcess("CgroupProcs", uid, pid, path)) { + path.clear(); + } + + return env->NewStringUTF(path.c_str()); +} + +const JNINativeMethod sMethods[] = { + {"nativeGetCgroupProcsPath", "(II)Ljava/lang/String;", (void*)getCgroupProcsPath}, +}; + +} // anonymous namespace + +int register_android_server_am_PhantomProcessList(JNIEnv* env) { + const char* className = "com/android/server/am/PhantomProcessList"; + return jniRegisterNativeMethods(env, className, sMethods, NELEM(sMethods)); +} + +} // namespace android
\ No newline at end of file diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 04642302ce45..2403934cbeb8 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -553,6 +553,9 @@ private: PointerIcon loadPointerIcon(JNIEnv* env, ui::LogicalDisplayId displayId, PointerIconStyle type); bool isDisplayInteractive(ui::LogicalDisplayId displayId); + // TODO(b/362719483) remove when the real topology is available + void populateFakeDisplayTopology(const std::vector<DisplayViewport>& viewports); + static inline JNIEnv* jniEnv() { return AndroidRuntime::getJNIEnv(); } }; @@ -641,6 +644,49 @@ void NativeInputManager::setDisplayViewports(JNIEnv* env, jobjectArray viewportO mInputManager->getChoreographer().setDisplayViewports(viewports); mInputManager->getReader().requestRefreshConfiguration( InputReaderConfiguration::Change::DISPLAY_INFO); + + // TODO(b/362719483) remove when the real topology is available + populateFakeDisplayTopology(viewports); +} + +void NativeInputManager::populateFakeDisplayTopology( + const std::vector<DisplayViewport>& viewports) { + if (!com::android::input::flags::connected_displays_cursor()) { + return; + } + + // create a fake topology assuming following order + // default-display (top-edge) -> next-display (right-edge) -> next-display (right-edge) ... + // This also adds a 100px offset on corresponding edge for better manual testing + // ┌────────┐ + // │ next ├─────────┐ + // ┌─└───────┐┤ next 2 │ ... + // │ default │└─────────┘ + // └─────────┘ + DisplayTopologyGraph displaytopology; + displaytopology.primaryDisplayId = ui::LogicalDisplayId::DEFAULT; + + // treat default display as base, in real topology it should be the primary-display + ui::LogicalDisplayId previousDisplay = ui::LogicalDisplayId::DEFAULT; + for (const auto& viewport : viewports) { + if (viewport.displayId == ui::LogicalDisplayId::DEFAULT) { + continue; + } + if (previousDisplay == ui::LogicalDisplayId::DEFAULT) { + displaytopology.graph[previousDisplay].push_back( + {viewport.displayId, DisplayTopologyPosition::TOP, 100}); + displaytopology.graph[viewport.displayId].push_back( + {previousDisplay, DisplayTopologyPosition::BOTTOM, -100}); + } else { + displaytopology.graph[previousDisplay].push_back( + {viewport.displayId, DisplayTopologyPosition::RIGHT, 100}); + displaytopology.graph[viewport.displayId].push_back( + {previousDisplay, DisplayTopologyPosition::LEFT, -100}); + } + previousDisplay = viewport.displayId; + } + + mInputManager->getChoreographer().setDisplayTopology(displaytopology); } void NativeInputManager::setDisplayTopology(JNIEnv* env, jobject topologyGraph) { diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index e3bd69c30de7..569383e71a06 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -72,6 +72,7 @@ int register_com_android_server_display_DisplayControl(JNIEnv* env); int register_com_android_server_SystemClockTime(JNIEnv* env); int register_android_server_display_smallAreaDetectionController(JNIEnv* env); int register_com_android_server_accessibility_BrailleDisplayConnection(JNIEnv* env); +int register_android_server_am_PhantomProcessList(JNIEnv* env); // Note: Consider adding new JNI entrypoints for optional services to // LazyJniRegistrar instead, and relying on lazy registration. @@ -139,5 +140,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_com_android_server_SystemClockTime(env); register_android_server_display_smallAreaDetectionController(env); register_com_android_server_accessibility_BrailleDisplayConnection(env); + register_android_server_am_PhantomProcessList(env); return JNI_VERSION_1_4; } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 60130d1f97be..f10b7b9a95a4 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -91,6 +91,7 @@ import android.server.ServerProtoEnums; import android.system.ErrnoException; import android.system.Os; import android.text.TextUtils; +import android.tracing.perfetto.InitArguments; import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.Dumpable; @@ -792,6 +793,12 @@ public final class SystemServer implements Dumpable { private void run() { TimingsTraceAndSlog t = new TimingsTraceAndSlog(); try { + if (android.tracing.Flags.systemServerLargePerfettoShmemBuffer()) { + // Explicitly initialize a 4 MB shmem buffer for Perfetto producers (b/382369925) + android.tracing.perfetto.Producer.init(new InitArguments( + InitArguments.PERFETTO_BACKEND_SYSTEM, 4 * 1024)); + } + t.traceBegin("InitBeforeStartServices"); // Record the process start information in sys props. @@ -3114,10 +3121,10 @@ public final class SystemServer implements Dumpable { if (com.android.ranging.flags.Flags.rangingStackEnabled()) { if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB) || context.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_WIFI_RTT) + PackageManager.FEATURE_WIFI_AWARE) || (com.android.ranging.flags.Flags.rangingCsEnabled() && context.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_BLUETOOTH_LE_CHANNEL_SOUNDING))) { + PackageManager.FEATURE_BLUETOOTH_LE))) { t.traceBegin("RangingService"); // TODO: b/375264320 - Remove after RELEASE_RANGING_STACK is ramped to next. try { diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java index c31594a4bf47..fc585c9e0f96 100644 --- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java +++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java @@ -16,6 +16,8 @@ package com.android.server.profcollect; +import static android.content.Intent.ACTION_BATTERY_LOW; +import static android.content.Intent.ACTION_BATTERY_OKAY; import static android.content.Intent.ACTION_SCREEN_OFF; import static android.content.Intent.ACTION_SCREEN_ON; @@ -77,6 +79,7 @@ public final class ProfcollectForwardingService extends SystemService { static boolean sVerityEnforced; static boolean sIsInteractive; static boolean sAdbActive; + static boolean sIsBatteryLow; private static IProfCollectd sIProfcollect; private static ProfcollectForwardingService sSelfService; @@ -91,7 +94,11 @@ public final class ProfcollectForwardingService extends SystemService { private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (ACTION_SCREEN_ON.equals(intent.getAction())) { + if (ACTION_BATTERY_LOW.equals(intent.getAction())) { + sIsBatteryLow = true; + } else if (ACTION_BATTERY_OKAY.equals(intent.getAction())) { + sIsBatteryLow = false; + } else if (ACTION_SCREEN_ON.equals(intent.getAction())) { Log.d(LOG_TAG, "Received broadcast that the device became interactive, was " + sIsInteractive); sIsInteractive = true; @@ -141,6 +148,8 @@ public final class ProfcollectForwardingService extends SystemService { context.getResources().getBoolean(R.bool.config_profcollectReportUploaderEnabled); final IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_BATTERY_LOW); + filter.addAction(ACTION_BATTERY_OKAY); filter.addAction(ACTION_SCREEN_ON); filter.addAction(ACTION_SCREEN_OFF); filter.addAction(INTENT_UPLOAD_PROFILES); diff --git a/services/profcollect/src/com/android/server/profcollect/Utils.java b/services/profcollect/src/com/android/server/profcollect/Utils.java index b754ca1875b6..c109f5cf05b6 100644 --- a/services/profcollect/src/com/android/server/profcollect/Utils.java +++ b/services/profcollect/src/com/android/server/profcollect/Utils.java @@ -118,6 +118,7 @@ final class Utils { } return ProfcollectForwardingService.sVerityEnforced && !ProfcollectForwardingService.sAdbActive - && ProfcollectForwardingService.sIsInteractive; + && ProfcollectForwardingService.sIsInteractive + && !ProfcollectForwardingService.sIsBatteryLow; } } 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 f96294ed4ca8..b7b4f0424165 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -214,7 +214,8 @@ public class DisplayManagerServiceTest { private static final String PACKAGE_NAME = "com.android.frameworks.displayservicetests"; private static final long STANDARD_DISPLAY_EVENTS = DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED - | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED; private static final long STANDARD_AND_CONNECTION_DISPLAY_EVENTS = STANDARD_DISPLAY_EVENTS @@ -222,7 +223,7 @@ public class DisplayManagerServiceTest { private static final String EVENT_DISPLAY_ADDED = "EVENT_DISPLAY_ADDED"; private static final String EVENT_DISPLAY_REMOVED = "EVENT_DISPLAY_REMOVED"; - private static final String EVENT_DISPLAY_CHANGED = "EVENT_DISPLAY_CHANGED"; + private static final String EVENT_DISPLAY_BASIC_CHANGED = "EVENT_DISPLAY_BASIC_CHANGED"; private static final String EVENT_DISPLAY_BRIGHTNESS_CHANGED = "EVENT_DISPLAY_BRIGHTNESS_CHANGED"; private static final String EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED = @@ -889,7 +890,6 @@ public class DisplayManagerServiceTest { FakeDisplayManagerCallback callback = registerDisplayListenerCallback( displayManager, bs, displayDevice); - // Simulate DisplayDevice change DisplayDeviceInfo displayDeviceInfo2 = new DisplayDeviceInfo(); displayDeviceInfo2.copyFrom(displayDeviceInfo); @@ -900,7 +900,8 @@ public class DisplayManagerServiceTest { Handler handler = displayManager.getDisplayHandler(); waitForIdleHandler(handler); - assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_CHANGED); + assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_BASIC_CHANGED, + EVENT_DISPLAY_REFRESH_RATE_CHANGED); } /** @@ -2145,7 +2146,7 @@ public class DisplayManagerServiceTest { new DisplayEventReceiver.FrameRateOverride(myUid, 30f), }); waitForIdleHandler(displayManager.getDisplayHandler()); - assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED); + assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED); callback.clear(); updateFrameRateOverride(displayManager, displayDevice, @@ -2154,7 +2155,7 @@ public class DisplayManagerServiceTest { new DisplayEventReceiver.FrameRateOverride(1234, 30f), }); waitForIdleHandler(displayManager.getDisplayHandler()); - assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_CHANGED); + assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_BASIC_CHANGED); updateFrameRateOverride(displayManager, displayDevice, new DisplayEventReceiver.FrameRateOverride[]{ @@ -2163,7 +2164,7 @@ public class DisplayManagerServiceTest { new DisplayEventReceiver.FrameRateOverride(5678, 30f), }); waitForIdleHandler(displayManager.getDisplayHandler()); - assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED); + assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED); callback.clear(); updateFrameRateOverride(displayManager, displayDevice, @@ -2172,7 +2173,7 @@ public class DisplayManagerServiceTest { new DisplayEventReceiver.FrameRateOverride(5678, 30f), }); waitForIdleHandler(displayManager.getDisplayHandler()); - assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED); + assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED); callback.clear(); updateFrameRateOverride(displayManager, displayDevice, @@ -2180,7 +2181,7 @@ public class DisplayManagerServiceTest { new DisplayEventReceiver.FrameRateOverride(5678, 30f), }); waitForIdleHandler(displayManager.getDisplayHandler()); - assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_CHANGED); + assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_BASIC_CHANGED); } /** @@ -2303,16 +2304,16 @@ public class DisplayManagerServiceTest { updateRenderFrameRate(displayManager, displayDevice, 30f); waitForIdleHandler(displayManager.getDisplayHandler()); - assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED); + assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_REFRESH_RATE_CHANGED); callback.clear(); updateRenderFrameRate(displayManager, displayDevice, 30f); waitForIdleHandler(displayManager.getDisplayHandler()); - assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_CHANGED); + assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_REFRESH_RATE_CHANGED); updateRenderFrameRate(displayManager, displayDevice, 20f); waitForIdleHandler(displayManager.getDisplayHandler()); - assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED); + assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_REFRESH_RATE_CHANGED); callback.clear(); } @@ -3888,7 +3889,7 @@ public class DisplayManagerServiceTest { observer.onChange(false, Settings.Secure.getUriFor(MIRROR_BUILT_IN_DISPLAY)); waitForIdleHandler(handler); - assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_CHANGED); + assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_BASIC_CHANGED); } @Test @@ -3919,7 +3920,7 @@ public class DisplayManagerServiceTest { observer.onChange(false, Settings.Secure.getUriFor(MIRROR_BUILT_IN_DISPLAY)); waitForIdleHandler(handler); - assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED); + assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED); } private void initDisplayPowerController(DisplayManagerInternal localService) { @@ -4389,8 +4390,8 @@ public class DisplayManagerServiceTest { return EVENT_DISPLAY_ADDED; case DisplayManagerGlobal.EVENT_DISPLAY_REMOVED: return EVENT_DISPLAY_REMOVED; - case DisplayManagerGlobal.EVENT_DISPLAY_CHANGED: - return EVENT_DISPLAY_CHANGED; + case DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED: + return EVENT_DISPLAY_BASIC_CHANGED; case DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED: return EVENT_DISPLAY_BRIGHTNESS_CHANGED; case DisplayManagerGlobal.EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED: diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt index 5d427139a857..c65024f8f9d5 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt @@ -26,6 +26,7 @@ import org.junit.Test import org.mockito.ArgumentMatchers.anyFloat import org.mockito.ArgumentMatchers.anyInt import org.mockito.kotlin.any +import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify @@ -43,7 +44,7 @@ class DisplayTopologyCoordinatorTest { @Before fun setUp() { - displayInfo.displayId = 2 + displayInfo.displayId = Display.DEFAULT_DISPLAY displayInfo.logicalWidth = 300 displayInfo.logicalHeight = 200 displayInfo.logicalDensityDpi = 100 @@ -90,6 +91,44 @@ class DisplayTopologyCoordinatorTest { } @Test + fun updateDisplay() { + whenever(mockTopology.updateDisplay(eq(Display.DEFAULT_DISPLAY), anyFloat(), anyFloat())) + .thenReturn(true) + + coordinator.onDisplayChanged(displayInfo) + + verify(mockTopologyChangedCallback).invoke(mockTopologyCopy) + } + + @Test + fun updateDisplay_notChanged() { + whenever(mockTopology.updateDisplay(eq(Display.DEFAULT_DISPLAY), anyFloat(), anyFloat())) + .thenReturn(false) + + coordinator.onDisplayChanged(displayInfo) + + verify(mockTopologyChangedCallback, never()).invoke(any()) + } + + @Test + fun removeDisplay() { + whenever(mockTopology.removeDisplay(Display.DEFAULT_DISPLAY)).thenReturn(true) + + coordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY) + + verify(mockTopologyChangedCallback).invoke(mockTopologyCopy) + } + + @Test + fun removeDisplay_notChanged() { + whenever(mockTopology.removeDisplay(Display.DEFAULT_DISPLAY)).thenReturn(false) + + coordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY) + + verify(mockTopologyChangedCallback, never()).invoke(any()) + } + + @Test fun getTopology_copy() { assertThat(coordinator.topology).isEqualTo(mockTopologyCopy) } 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 ad30f22fe060..0dbb6ba58b3c 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -36,9 +36,9 @@ import static com.android.server.display.DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DE import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED; import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CONNECTED; import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_DISCONNECTED; -import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED; import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED; import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_STATE_CHANGED; +import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED; import static com.android.server.display.layout.Layout.Display.POSITION_REAR; import static com.android.server.display.layout.Layout.Display.POSITION_UNKNOWN; import static com.android.server.utils.FoldSettingProvider.SETTING_VALUE_SELECTIVE_STAY_AWAKE; @@ -1170,18 +1170,20 @@ public class LogicalDisplayMapperTest { @Test public void updateAndGetMaskForDisplayPropertyChanges_getsPropertyChangedFlags() { - // Change the display state + // Change the refresh rate override DisplayInfo newDisplayInfo = new DisplayInfo(); - newDisplayInfo.state = STATE_OFF; - assertEquals(LOGICAL_DISPLAY_EVENT_STATE_CHANGED, + newDisplayInfo.refreshRateOverride = 30; + assertEquals(LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED, mLogicalDisplayMapper.updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo)); - // Change the refresh rate override + // Change the display state + when(mFlagsMock.isDisplayListenerPerformanceImprovementsEnabled()).thenReturn(true); newDisplayInfo = new DisplayInfo(); - newDisplayInfo.refreshRateOverride = 30; - assertEquals(LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED, + newDisplayInfo.state = STATE_OFF; + assertEquals(LOGICAL_DISPLAY_EVENT_STATE_CHANGED, mLogicalDisplayMapper.updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo)); + // Change multiple properties newDisplayInfo = new DisplayInfo(); newDisplayInfo.refreshRateOverride = 30; diff --git a/services/tests/mockingservicestests/jni/Android.bp b/services/tests/mockingservicestests/jni/Android.bp index 94d4b9522d60..03bd73c52083 100644 --- a/services/tests/mockingservicestests/jni/Android.bp +++ b/services/tests/mockingservicestests/jni/Android.bp @@ -24,6 +24,7 @@ cc_library_shared { ":lib_freezer_native", ":lib_oomConnection_native", ":lib_lazilyRegisteredServices_native", + ":lib_phantomProcessList_native", "onload.cpp", ], diff --git a/services/tests/mockingservicestests/jni/onload.cpp b/services/tests/mockingservicestests/jni/onload.cpp index 9b4c8178b092..30fa7de94af1 100644 --- a/services/tests/mockingservicestests/jni/onload.cpp +++ b/services/tests/mockingservicestests/jni/onload.cpp @@ -28,6 +28,7 @@ int register_android_server_am_CachedAppOptimizer(JNIEnv* env); int register_android_server_am_Freezer(JNIEnv* env); int register_android_server_am_OomConnection(JNIEnv* env); int register_android_server_utils_LazyJniRegistrar(JNIEnv* env); +int register_android_server_am_PhantomProcessList(JNIEnv* env); }; using namespace android; @@ -46,5 +47,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_am_Freezer(env); register_android_server_am_OomConnection(env); register_android_server_utils_LazyJniRegistrar(env); + register_android_server_am_PhantomProcessList(env); return JNI_VERSION_1_4; } diff --git a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java index 4b2e850d08e7..35f421e582d8 100644 --- a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java +++ b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java @@ -70,9 +70,12 @@ import android.os.PerformanceHintManager; import android.os.Process; import android.os.RemoteException; import android.os.SessionCreationConfig; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.Log; import com.android.server.FgThread; @@ -89,6 +92,8 @@ import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +import java.io.BufferedReader; +import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -160,6 +165,8 @@ public class HintManagerServiceTest { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private HintManagerService mService; private ChannelConfig mConfig; @@ -1322,6 +1329,7 @@ public class HintManagerServiceTest { @Test + @EnableFlags({Flags.FLAG_CPU_HEADROOM_AFFINITY_CHECK}) public void testCpuHeadroomCache() throws Exception { CpuHeadroomParamsInternal params1 = new CpuHeadroomParamsInternal(); CpuHeadroomParams halParams1 = new CpuHeadroomParams(); @@ -1335,11 +1343,14 @@ public class HintManagerServiceTest { halParams2.calculationType = CpuHeadroomParams.CalculationType.MIN; halParams2.tids = new int[]{}; + CountDownLatch latch = new CountDownLatch(2); + int[] tids = createThreads(2, latch); CpuHeadroomParamsInternal params3 = new CpuHeadroomParamsInternal(); + params3.tids = tids; params3.calculationType = CpuHeadroomParams.CalculationType.AVERAGE; CpuHeadroomParams halParams3 = new CpuHeadroomParams(); + halParams3.tids = tids; halParams3.calculationType = CpuHeadroomParams.CalculationType.AVERAGE; - halParams3.tids = new int[]{Process.myPid()}; // this params should not be cached as the window is not default CpuHeadroomParamsInternal params4 = new CpuHeadroomParamsInternal(); @@ -1411,6 +1422,65 @@ public class HintManagerServiceTest { verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams2)); verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams3)); verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4)); + latch.countDown(); + } + + @Test + @EnableFlags({Flags.FLAG_CPU_HEADROOM_AFFINITY_CHECK}) + public void testGetCpuHeadroomDifferentAffinity_flagOn() throws Exception { + CountDownLatch latch = new CountDownLatch(2); + int[] tids = createThreads(2, latch); + CpuHeadroomParamsInternal params = new CpuHeadroomParamsInternal(); + params.tids = tids; + CpuHeadroomParams halParams = new CpuHeadroomParams(); + halParams.tids = tids; + float headroom = 0.1f; + CpuHeadroomResult halRet = CpuHeadroomResult.globalHeadroom(headroom); + String ret1 = runAndWaitForCommand("taskset -p 1 " + tids[0]); + String ret2 = runAndWaitForCommand("taskset -p 3 " + tids[1]); + + HintManagerService service = createService(); + clearInvocations(mIPowerMock); + when(mIPowerMock.getCpuHeadroom(eq(halParams))).thenReturn(halRet); + assertThrows("taskset cmd return: " + ret1 + "\n" + ret2, IllegalStateException.class, + () -> service.getBinderServiceInstance().getCpuHeadroom(params)); + verify(mIPowerMock, times(0)).getCpuHeadroom(any()); + } + + @Test + @DisableFlags({Flags.FLAG_CPU_HEADROOM_AFFINITY_CHECK}) + public void testGetCpuHeadroomDifferentAffinity_flagOff() throws Exception { + CountDownLatch latch = new CountDownLatch(2); + int[] tids = createThreads(2, latch); + CpuHeadroomParamsInternal params = new CpuHeadroomParamsInternal(); + params.tids = tids; + CpuHeadroomParams halParams = new CpuHeadroomParams(); + halParams.tids = tids; + float headroom = 0.1f; + CpuHeadroomResult halRet = CpuHeadroomResult.globalHeadroom(headroom); + String ret1 = runAndWaitForCommand("taskset -p 1 " + tids[0]); + String ret2 = runAndWaitForCommand("taskset -p 3 " + tids[1]); + + HintManagerService service = createService(); + clearInvocations(mIPowerMock); + when(mIPowerMock.getCpuHeadroom(eq(halParams))).thenReturn(halRet); + assertEquals("taskset cmd return: " + ret1 + "\n" + ret2, halRet, + service.getBinderServiceInstance().getCpuHeadroom(params)); + verify(mIPowerMock, times(1)).getCpuHeadroom(any()); + } + + private String runAndWaitForCommand(String command) throws Exception { + java.lang.Process process = Runtime.getRuntime().exec(command); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + StringBuilder res = new StringBuilder(); + while ((line = reader.readLine()) != null) { + res.append(line); + } + process.waitFor(); + // somehow the exit code can be 1 for the taskset command though it exits successfully, + // thus we just return the output + return res.toString(); } @Test diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java index 96741e0b1e87..469bd66b7e7b 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java @@ -21,6 +21,7 @@ import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; @@ -889,6 +890,32 @@ public class NotifierTest { "my.package.name", false, null, null); } + @Test + public void getWakelockMonitorTypeForLogging_evaluatesWakelockLevel() { + createNotifier(); + assertEquals(mNotifier.getWakelockMonitorTypeForLogging(PowerManager.SCREEN_DIM_WAKE_LOCK), + PowerManager.FULL_WAKE_LOCK); + assertEquals(mNotifier.getWakelockMonitorTypeForLogging( + PowerManager.SCREEN_BRIGHT_WAKE_LOCK), PowerManager.FULL_WAKE_LOCK); + assertEquals(mNotifier.getWakelockMonitorTypeForLogging(PowerManager.DRAW_WAKE_LOCK), + PowerManager.DRAW_WAKE_LOCK); + assertEquals(mNotifier.getWakelockMonitorTypeForLogging(PowerManager.PARTIAL_WAKE_LOCK), + PowerManager.PARTIAL_WAKE_LOCK); + assertEquals(mNotifier.getWakelockMonitorTypeForLogging( + PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK), + PowerManager.PARTIAL_WAKE_LOCK); + assertEquals(mNotifier.getWakelockMonitorTypeForLogging( + PowerManager.DOZE_WAKE_LOCK), -1); + + when(mResourcesSpy.getBoolean( + com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity)) + .thenReturn(true); + + createNotifier(); + assertEquals(mNotifier.getWakelockMonitorTypeForLogging( + PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK), -1); + } + private final PowerManagerService.Injector mInjector = new PowerManagerService.Injector() { @Override Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats, diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 2fe6918630f6..7dbbff22a537 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -33,6 +33,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.server.am.UserController.CLEAR_USER_JOURNEY_SESSION_MSG; import static com.android.server.am.UserController.COMPLETE_USER_SWITCH_MSG; import static com.android.server.am.UserController.CONTINUE_USER_SWITCH_MSG; +import static com.android.server.am.UserController.DEFAULT_BEFORE_USER_SWITCH_TIMEOUT_MS; import static com.android.server.am.UserController.REPORT_LOCKED_BOOT_COMPLETE_MSG; import static com.android.server.am.UserController.REPORT_USER_SWITCH_COMPLETE_MSG; import static com.android.server.am.UserController.REPORT_USER_SWITCH_MSG; @@ -94,6 +95,7 @@ import android.os.Looper; import android.os.Message; import android.os.PowerManagerInternal; import android.os.RemoteException; +import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.IStorageManager; @@ -181,14 +183,12 @@ public class UserControllerTest { Intent.ACTION_USER_STARTING); private static final Set<Integer> START_FOREGROUND_USER_MESSAGE_CODES = newHashSet( - 0, // for startUserInternalOnHandler REPORT_USER_SWITCH_MSG, USER_SWITCH_TIMEOUT_MSG, USER_START_MSG, USER_CURRENT_MSG); private static final Set<Integer> START_BACKGROUND_USER_MESSAGE_CODES = newHashSet( - 0, // for startUserInternalOnHandler USER_START_MSG, REPORT_LOCKED_BOOT_COMPLETE_MSG); @@ -376,7 +376,7 @@ public class UserControllerTest { // and the cascade effect goes on...). In fact, a better approach would to not assert the // binder calls, but their side effects (in this case, that the user is stopped right away) assertWithMessage("wrong binder message calls").that(mInjector.mHandler.getMessageCodes()) - .containsExactly(/* for startUserInternalOnHandler */ 0, USER_START_MSG); + .containsExactly(USER_START_MSG); } private void startUserAssertions( @@ -419,17 +419,12 @@ public class UserControllerTest { @Test public void testDispatchUserSwitch() throws RemoteException { // Prepare mock observer and register it - IUserSwitchObserver observer = mock(IUserSwitchObserver.class); - when(observer.asBinder()).thenReturn(new Binder()); - doAnswer(invocation -> { - IRemoteCallback callback = (IRemoteCallback) invocation.getArguments()[1]; - callback.sendResult(null); - return null; - }).when(observer).onUserSwitching(anyInt(), any()); - mUserController.registerUserSwitchObserver(observer, "mock"); + IUserSwitchObserver observer = registerUserSwitchObserver( + /* replyToOnBeforeUserSwitchingCallback= */ true, + /* replyToOnUserSwitchingCallback= */ true); // Start user -- this will update state of mUserController mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); - verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID)); + verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID), any()); Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG); assertNotNull(reportMsg); UserState userState = (UserState) reportMsg.obj; @@ -453,14 +448,26 @@ public class UserControllerTest { } @Test + public void testShouldCrashWhenOnBeforeUserSwitchingTimeouts() throws RemoteException { + IUserSwitchObserver observer = registerUserSwitchObserver( + /* replyToOnBeforeUserSwitchingCallback= */ false, + /* replyToOnUserSwitchingCallback= */ true); + mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); + verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID), any()); + assertThrows("Should have crashed when observers don't reply to onBeforeUserSwitching in " + + DEFAULT_BEFORE_USER_SWITCH_TIMEOUT_MS + " ms", RuntimeException.class, + mInjector.mHandler::runPendingCallbacks); + } + + @Test public void testDispatchUserSwitchBadReceiver() throws RemoteException { - // Prepare mock observer which doesn't notify the callback and register it - IUserSwitchObserver observer = mock(IUserSwitchObserver.class); - when(observer.asBinder()).thenReturn(new Binder()); - mUserController.registerUserSwitchObserver(observer, "mock"); + // Prepare mock observer which doesn't notify the onUserSwitching callback and register it + IUserSwitchObserver observer = registerUserSwitchObserver( + /* replyToOnBeforeUserSwitchingCallback= */ true, + /* replyToOnUserSwitchingCallback= */ false); // Start user -- this will update state of mUserController mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); - verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID)); + verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID), any()); Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG); assertNotNull(reportMsg); UserState userState = (UserState) reportMsg.obj; @@ -551,7 +558,6 @@ public class UserControllerTest { expectedCodes.add(REPORT_USER_SWITCH_COMPLETE_MSG); if (backgroundUserStopping) { expectedCodes.add(CLEAR_USER_JOURNEY_SESSION_MSG); - expectedCodes.add(0); // this is for directly posting in stopping. } if (expectScheduleBackgroundUserStopping) { expectedCodes.add(SCHEDULED_STOP_BACKGROUND_USER_MSG); @@ -567,9 +573,9 @@ public class UserControllerTest { @Test public void testDispatchUserSwitchComplete() throws RemoteException { // Prepare mock observer and register it - IUserSwitchObserver observer = mock(IUserSwitchObserver.class); - when(observer.asBinder()).thenReturn(new Binder()); - mUserController.registerUserSwitchObserver(observer, "mock"); + IUserSwitchObserver observer = registerUserSwitchObserver( + /* replyToOnBeforeUserSwitchingCallback= */ true, + /* replyToOnUserSwitchingCallback= */ true); // Start user -- this will update state of mUserController mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG); @@ -1752,6 +1758,29 @@ public class UserControllerTest { verify(mInjector, never()).onSystemUserVisibilityChanged(anyBoolean()); } + private IUserSwitchObserver registerUserSwitchObserver( + boolean replyToOnBeforeUserSwitchingCallback, boolean replyToOnUserSwitchingCallback) + throws RemoteException { + IUserSwitchObserver observer = mock(IUserSwitchObserver.class); + when(observer.asBinder()).thenReturn(new Binder()); + if (replyToOnBeforeUserSwitchingCallback) { + doAnswer(invocation -> { + IRemoteCallback callback = (IRemoteCallback) invocation.getArguments()[1]; + callback.sendResult(null); + return null; + }).when(observer).onBeforeUserSwitching(anyInt(), any()); + } + if (replyToOnUserSwitchingCallback) { + doAnswer(invocation -> { + IRemoteCallback callback = (IRemoteCallback) invocation.getArguments()[1]; + callback.sendResult(null); + return null; + }).when(observer).onUserSwitching(anyInt(), any()); + } + mUserController.registerUserSwitchObserver(observer, "mock"); + return observer; + } + // Should be public to allow mocking private static class TestInjector extends UserController.Injector { public final TestHandler mHandler; @@ -1957,6 +1986,7 @@ public class UserControllerTest { * fix this, but in the meantime, this is your warning. */ private final List<Message> mMessages = new ArrayList<>(); + private final List<Runnable> mPendingCallbacks = new ArrayList<>(); TestHandler(Looper looper) { super(looper); @@ -1989,14 +2019,24 @@ public class UserControllerTest { @Override public boolean sendMessageAtTime(Message msg, long uptimeMillis) { - Message copy = new Message(); - copy.copyFrom(msg); - mMessages.add(copy); - if (msg.getCallback() != null) { - msg.getCallback().run(); + if (msg.getCallback() == null) { + Message copy = new Message(); + copy.copyFrom(msg); + mMessages.add(copy); + } else { + if (SystemClock.uptimeMillis() >= uptimeMillis) { + msg.getCallback().run(); + } else { + mPendingCallbacks.add(msg.getCallback()); + } msg.setCallback(null); } return super.sendMessageAtTime(msg, uptimeMillis); } + + private void runPendingCallbacks() { + mPendingCallbacks.forEach(Runnable::run); + mPendingCallbacks.clear(); + } } } diff --git a/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java b/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java index 9c61d95bc5e5..9528a05d38a0 100644 --- a/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java +++ b/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java @@ -23,13 +23,13 @@ import android.test.AndroidTestCase; import android.util.Pair; import android.util.Xml; -import com.android.internal.util.FastXmlSerializer; import com.android.modules.utils.TypedXmlSerializer; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.xmlpull.v1.XmlPullParserException; import java.io.ByteArrayInputStream; import java.io.StringWriter; @@ -123,8 +123,24 @@ public class CacheQuotaStrategyTest extends AndroidTestCase { buildCacheQuotaHint("uuid2", 10, 250)); } + @Test + public void testReadInvalidInput() throws Exception { + String input = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" + + "<cache-info previousBytes=\"1000\">\n" + + "<quota/>\n" + + "</cache-info>\n"; + + try { + CacheQuotaStrategy.readFromXml(new ByteArrayInputStream( + input.getBytes("UTF-8"))); + fail("Expected XML parsing exception"); + } catch (XmlPullParserException e) { + // Expected XmlPullParserException exception + } + } + private CacheQuotaHint buildCacheQuotaHint(String volumeUuid, int uid, long quota) { return new CacheQuotaHint.Builder() .setVolumeUuid(volumeUuid).setUid(uid).setQuota(quota).build(); } -}
\ No newline at end of file +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java index decbaacdcef9..d1dc8d6e81c8 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java @@ -274,6 +274,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase { assertEquals(new ArraySet<>(), approved.get(true)); } + @SuppressWarnings("GuardedBy") @Test public void testReadXml_userDisabled_restore() throws Exception { String xml = "<enabled_assistants version=\"4\" defaults=\"b/b\">" @@ -289,7 +290,8 @@ public class NotificationAssistantsTest extends UiServiceTestCase { mAssistants.readXml(parser, mNm::canUseManagedServices, true, ActivityManager.getCurrentUser()); - ArrayMap<Boolean, ArraySet<String>> approved = mAssistants.mApproved.get(0); + ArrayMap<Boolean, ArraySet<String>> approved = mAssistants.mApproved.get( + ActivityManager.getCurrentUser()); // approved should not be null assertNotNull(approved); 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 20f4bb65d27b..601023f89656 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -17683,4 +17683,145 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(mService.mNotificationList).isEmpty(); } + + @Test + @EnableFlags({FLAG_NOTIFICATION_CLASSIFICATION, + FLAG_NOTIFICATION_FORCE_GROUPING, + FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION}) + public void testUnbundleNotification_ungrouped_restoresOriginalChannel() throws Exception { + NotificationManagerService.WorkerHandler handler = mock( + NotificationManagerService.WorkerHandler.class); + mService.setHandler(handler); + when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); + when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true); + when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true); + when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true); + + // Post a single notification + final boolean hasOriginalSummary = false; + final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); + final String keyToUnbundle = r.getKey(); + mService.addNotification(r); + + // Classify notification into the NEWS bundle + Bundle signals = new Bundle(); + signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_NEWS); + Adjustment adjustment = new Adjustment( + r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); + mBinderService.applyAdjustmentFromAssistant(null, adjustment); + waitForIdle(); + r.applyAdjustments(); + // Check that the NotificationRecord channel is updated + assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID); + // Check that the Notification mChannelId is not updated + assertThat(r.getNotification().getChannelId()).isEqualTo(TEST_CHANNEL_ID); + + // Unbundle the notification + mService.mNotificationDelegate.unbundleNotification(keyToUnbundle); + + // Check that the original channel was restored + assertThat(r.getChannel().getId()).isEqualTo(TEST_CHANNEL_ID); + verify(mGroupHelper, times(1)).onNotificationUnbundled(eq(r), eq(hasOriginalSummary)); + } + + @Test + @EnableFlags({FLAG_NOTIFICATION_CLASSIFICATION, + FLAG_NOTIFICATION_FORCE_GROUPING, + FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION}) + public void testUnbundleNotification_grouped_restoresOriginalChannel() throws Exception { + NotificationManagerService.WorkerHandler handler = mock( + NotificationManagerService.WorkerHandler.class); + mService.setHandler(handler); + when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); + when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true); + when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true); + when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true); + + // Post grouped notifications + final String originalGroupName = "originalGroup"; + final int summaryId = 0; + final NotificationRecord r1 = generateNotificationRecord(mTestNotificationChannel, + summaryId + 1, originalGroupName, false); + mService.addNotification(r1); + final NotificationRecord r2 = generateNotificationRecord(mTestNotificationChannel, + summaryId + 2, originalGroupName, false); + mService.addNotification(r2); + final NotificationRecord summary = generateNotificationRecord(mTestNotificationChannel, + summaryId, originalGroupName, true); + mService.addNotification(summary); + final String originalGroupKey = summary.getGroupKey(); + assertThat(mService.mSummaryByGroupKey).containsEntry(originalGroupKey, summary); + + // Classify a child notification into the NEWS bundle + final String keyToUnbundle = r1.getKey(); + final boolean hasOriginalSummary = true; + Bundle signals = new Bundle(); + signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_NEWS); + Adjustment adjustment = new Adjustment(r1.getSbn().getPackageName(), r1.getKey(), signals, + "", r1.getUser().getIdentifier()); + mBinderService.applyAdjustmentFromAssistant(null, adjustment); + waitForIdle(); + r1.applyAdjustments(); + assertThat(r1.getChannel().getId()).isEqualTo(NEWS_ID); + + // Unbundle the notification + mService.mNotificationDelegate.unbundleNotification(keyToUnbundle); + + // Check that the original channel was restored + assertThat(r1.getChannel().getId()).isEqualTo(TEST_CHANNEL_ID); + verify(mGroupHelper, times(1)).onNotificationUnbundled(eq(r1), eq(hasOriginalSummary)); + } + + @Test + @EnableFlags({FLAG_NOTIFICATION_CLASSIFICATION, + FLAG_NOTIFICATION_FORCE_GROUPING, + FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION}) + public void testUnbundleNotification_groupedSummaryCanceled_restoresOriginalChannel() + throws Exception { + NotificationManagerService.WorkerHandler handler = mock( + NotificationManagerService.WorkerHandler.class); + mService.setHandler(handler); + when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); + when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true); + when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true); + when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true); + + // Post grouped notifications + final String originalGroupName = "originalGroup"; + final int summaryId = 0; + final NotificationRecord r1 = generateNotificationRecord(mTestNotificationChannel, + summaryId + 1, originalGroupName, false); + mService.addNotification(r1); + final NotificationRecord r2 = generateNotificationRecord(mTestNotificationChannel, + summaryId + 2, originalGroupName, false); + mService.addNotification(r2); + final NotificationRecord summary = generateNotificationRecord(mTestNotificationChannel, + summaryId, originalGroupName, true); + mService.addNotification(summary); + final String originalGroupKey = summary.getGroupKey(); + assertThat(mService.mSummaryByGroupKey).containsEntry(originalGroupKey, summary); + + // Classify a child notification into the NEWS bundle + final String keyToUnbundle = r1.getKey(); + Bundle signals = new Bundle(); + signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_NEWS); + Adjustment adjustment = new Adjustment(r1.getSbn().getPackageName(), r1.getKey(), signals, + "", r1.getUser().getIdentifier()); + mBinderService.applyAdjustmentFromAssistant(null, adjustment); + waitForIdle(); + r1.applyAdjustments(); + assertThat(r1.getChannel().getId()).isEqualTo(NEWS_ID); + + // Cancel original summary + final boolean hasOriginalSummary = false; + mService.mSummaryByGroupKey.remove(summary.getGroupKey()); + + // Unbundle the notification + mService.mNotificationDelegate.unbundleNotification(keyToUnbundle); + + // Check that the original channel was restored + assertThat(r1.getChannel().getId()).isEqualTo(TEST_CHANNEL_ID); + verify(mGroupHelper, times(1)).onNotificationUnbundled(eq(r1), eq(hasOriginalSummary)); + } + } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index fbd53f714dbf..8e79514c875e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -66,7 +66,6 @@ import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.No import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED; -import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL; import static com.android.server.notification.Flags.FLAG_NOTIFICATION_VERIFY_CHANNEL_SOUND_URI; import static com.android.server.notification.Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA; import static com.android.server.notification.NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED_BY_USER; @@ -155,7 +154,6 @@ import android.util.proto.ProtoOutputStream; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; import com.android.internal.config.sysui.TestableFlagResolver; @@ -167,9 +165,6 @@ import com.android.os.AtomsProto.PackageNotificationPreferences; import com.android.server.UiServiceTestCase; import com.android.server.notification.PermissionHelper.PackagePermission; -import platform.test.runner.parameterized.ParameterizedAndroidJunit4; -import platform.test.runner.parameterized.Parameters; - import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.protobuf.InvalidProtocolBufferException; @@ -204,6 +199,9 @@ import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadLocalRandom; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + @SmallTest @RunWith(ParameterizedAndroidJunit4.class) @EnableFlags(FLAG_PERSIST_INCOMPLETE_RESTORE_DATA) @@ -2640,6 +2638,35 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test + public void getPackagesBypassingDnd_multipleUsers() { + int uidUser1 = UserHandle.getUid(1, UID_P); + NotificationChannel channelUser1Bypass = new NotificationChannel("id11", "name1", + NotificationManager.IMPORTANCE_MAX); + channelUser1Bypass.setBypassDnd(true); + NotificationChannel channelUser1NoBypass = new NotificationChannel("id12", "name2", + NotificationManager.IMPORTANCE_MAX); + channelUser1NoBypass.setBypassDnd(false); + + int uidUser2 = UserHandle.getUid(2, UID_P); + NotificationChannel channelUser2Bypass = new NotificationChannel("id21", "name1", + NotificationManager.IMPORTANCE_MAX); + channelUser2Bypass.setBypassDnd(true); + + mHelper.createNotificationChannel(PKG_P, uidUser1, channelUser1Bypass, true, + /* hasDndAccess= */ true, uidUser1, false); + mHelper.createNotificationChannel(PKG_P, uidUser1, channelUser1NoBypass, true, + /* hasDndAccess= */ true, uidUser1, false); + mHelper.createNotificationChannel(PKG_P, uidUser2, channelUser2Bypass, true, + /* hasDndAccess= */ true, uidUser2, false); + + assertThat(mHelper.getPackagesBypassingDnd(0)).isEmpty(); + assertThat(mHelper.getPackagesBypassingDnd(1)) + .containsExactly(new ZenBypassingApp(PKG_P, false)); + assertThat(mHelper.getPackagesBypassingDnd(2)) + .containsExactly(new ZenBypassingApp(PKG_P, true)); + } + + @Test public void getPackagesBypassingDnd_oneChannelBypassing_groupBlocked() { int uid = UID_N_MR1; NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1"); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java index 670f9f697a5c..bacf5ed9d81f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java @@ -53,7 +53,9 @@ import android.content.pm.UserPackage; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.DexmakerShareClassLoaderRule; import android.util.Pair; import android.util.SparseArray; @@ -66,6 +68,7 @@ import com.android.internal.app.SuspendedAppActivity; import com.android.internal.app.UnlaunchableAppActivity; import com.android.server.LocalServices; import com.android.server.am.ActivityManagerService; +import com.android.window.flags.Flags; import org.junit.After; import org.junit.Before; @@ -133,6 +136,8 @@ public class ActivityStartInterceptorTest { private SparseArray<ActivityInterceptorCallback> mActivityInterceptorCallbacks = new SparseArray<>(); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Before public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); @@ -237,6 +242,20 @@ public class ActivityStartInterceptorTest { } @Test + @EnableFlags(Flags.FLAG_NORMALIZE_HOME_INTENT) + public void testInterceptIncorrectHomeIntent() { + // Create a non-standard home intent + final Intent homeIntent = new Intent(Intent.ACTION_MAIN); + homeIntent.addCategory(Intent.CATEGORY_HOME); + homeIntent.addCategory(Intent.CATEGORY_LAUNCHER); + + // Ensure the intent is intercepted and normalized to standard home intent. + assertTrue(mInterceptor.intercept(homeIntent, null, mAInfo, null, null, null, 0, 0, null, + mTaskDisplayArea, false)); + assertTrue(ActivityRecord.isHomeIntent(homeIntent)); + } + + @Test public void testInterceptLockTaskModeViolationPackage() { when(mLockTaskController.isActivityAllowed( TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT)) diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java index 0a7df5a305bc..0af41ea1f634 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java @@ -450,7 +450,7 @@ public class DisplayAreaTest extends WindowTestsBase { public void testGetOrientation() { final DisplayArea.Tokens area = new DisplayArea.Tokens(mWm, ABOVE_TASKS, "test"); mDisplayContent.addChild(area, POSITION_TOP); - final WindowState win = createWindow(null, TYPE_APPLICATION_OVERLAY, "overlay"); + final WindowState win = newWindowBuilder("overlay", TYPE_APPLICATION_OVERLAY).build(); win.mAttrs.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; win.mToken.reparent(area, POSITION_TOP); spyOn(win); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index db71f2bf039d..57aacd36b16b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -178,8 +178,8 @@ public class DisplayContentTests extends WindowTestsBase { @SetupWindows(addAllCommonWindows = true) @Test public void testForAllWindows() { - final WindowState exitingAppWindow = createWindow(null, TYPE_BASE_APPLICATION, - mDisplayContent, "exiting app"); + final WindowState exitingAppWindow = newWindowBuilder("exiting app", + TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); final ActivityRecord exitingApp = exitingAppWindow.mActivityRecord; exitingApp.startAnimation(exitingApp.getPendingTransaction(), mock(AnimationAdapter.class), false /* hidden */, SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION); @@ -211,8 +211,8 @@ public class DisplayContentTests extends WindowTestsBase { @SetupWindows(addAllCommonWindows = true) @Test public void testForAllWindows_WithAppImeTarget() { - final WindowState imeAppTarget = - createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget"); + final WindowState imeAppTarget = newWindowBuilder("imeAppTarget", + TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); mDisplayContent.setImeLayeringTarget(imeAppTarget); @@ -289,8 +289,8 @@ public class DisplayContentTests extends WindowTestsBase { public void testForAllWindows_WithInBetweenWindowToken() { // This window is set-up to be z-ordered between some windows that go in the same token like // the nav bar and status bar. - final WindowState voiceInteractionWindow = createWindow(null, TYPE_VOICE_INTERACTION, - mDisplayContent, "voiceInteractionWindow"); + final WindowState voiceInteractionWindow = newWindowBuilder("voiceInteractionWindow", + TYPE_VOICE_INTERACTION).setDisplay(mDisplayContent).build(); assertForAllWindowsOrder(Arrays.asList( mWallpaperWindow, @@ -310,7 +310,8 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testComputeImeTarget() { // Verify that an app window can be an ime target. - final WindowState appWin = createWindow(null, TYPE_APPLICATION, mDisplayContent, "appWin"); + final WindowState appWin = newWindowBuilder("appWin", TYPE_APPLICATION).setDisplay( + mDisplayContent).build(); appWin.setHasSurface(true); assertTrue(appWin.canBeImeTarget()); WindowState imeTarget = mDisplayContent.computeImeTarget(false /* updateImeTarget */); @@ -318,8 +319,8 @@ public class DisplayContentTests extends WindowTestsBase { appWin.mHidden = false; // Verify that an child window can be an ime target. - final WindowState childWin = createWindow(appWin, - TYPE_APPLICATION_ATTACHED_DIALOG, "childWin"); + final WindowState childWin = newWindowBuilder("childWin", + TYPE_APPLICATION_ATTACHED_DIALOG).setParent(appWin).build(); childWin.setHasSurface(true); assertTrue(childWin.canBeImeTarget()); imeTarget = mDisplayContent.computeImeTarget(false /* updateImeTarget */); @@ -331,8 +332,8 @@ public class DisplayContentTests extends WindowTestsBase { public void testComputeImeTarget_startingWindow() { ActivityRecord activity = createActivityRecord(mDisplayContent); - final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, activity, - "startingWin"); + final WindowState startingWin = newWindowBuilder("startingWin", + TYPE_APPLICATION_STARTING).setWindowToken(activity).build(); startingWin.setHasSurface(true); assertTrue(startingWin.canBeImeTarget()); @@ -342,7 +343,8 @@ public class DisplayContentTests extends WindowTestsBase { // Verify that the starting window still be an ime target even an app window launching // behind it. - final WindowState appWin = createWindow(null, TYPE_BASE_APPLICATION, activity, "appWin"); + final WindowState appWin = newWindowBuilder("appWin", TYPE_BASE_APPLICATION).setWindowToken( + activity).build(); appWin.setHasSurface(true); assertTrue(appWin.canBeImeTarget()); @@ -352,8 +354,8 @@ public class DisplayContentTests extends WindowTestsBase { // Verify that the starting window still be an ime target even the child window behind a // launching app window - final WindowState childWin = createWindow(appWin, - TYPE_APPLICATION_ATTACHED_DIALOG, "childWin"); + final WindowState childWin = newWindowBuilder("childWin", + TYPE_APPLICATION_ATTACHED_DIALOG).setParent(appWin).build(); childWin.setHasSurface(true); assertTrue(childWin.canBeImeTarget()); imeTarget = mDisplayContent.computeImeTarget(false /* updateImeTarget */); @@ -365,8 +367,8 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayArea.Tokens imeContainer = mDisplayContent.getImeContainer(); final ActivityRecord activity = createActivityRecord(mDisplayContent); - final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, activity, - "startingWin"); + final WindowState startingWin = newWindowBuilder("startingWin", + TYPE_APPLICATION_STARTING).setWindowToken(activity).build(); startingWin.setHasSurface(true); assertTrue(startingWin.canBeImeTarget()); final WindowContainer imeSurfaceParentWindow = mock(WindowContainer.class); @@ -385,10 +387,10 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testComputeImeTargetReturnsNull_windowDidntRequestIme() { - final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, - new ActivityBuilder(mAtm).setCreateTask(true).build(), "app"); - final WindowState win2 = createWindow(null, TYPE_BASE_APPLICATION, - new ActivityBuilder(mAtm).setCreateTask(true).build(), "app2"); + final WindowState win1 = newWindowBuilder("app", TYPE_BASE_APPLICATION).setWindowToken( + new ActivityBuilder(mAtm).setCreateTask(true).build()).build(); + final WindowState win2 = newWindowBuilder("app2", TYPE_BASE_APPLICATION).setWindowToken( + new ActivityBuilder(mAtm).setCreateTask(true).build()).build(); mDisplayContent.setImeInputTarget(win1); mDisplayContent.setImeLayeringTarget(win2); @@ -404,8 +406,8 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayArea.Tokens imeContainer = mDisplayContent.getImeContainer(); final ActivityRecord activity = createActivityRecord(mDisplayContent); - final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, activity, - "startingWin"); + final WindowState startingWin = newWindowBuilder("startingWin", + TYPE_APPLICATION_STARTING).setWindowToken(activity).build(); startingWin.setHasSurface(true); assertTrue(startingWin.canBeImeTarget()); final WindowContainer imeSurfaceParentWindow = mock(WindowContainer.class); @@ -433,8 +435,8 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayArea.Tokens imeContainer = mDisplayContent.getImeContainer(); final ActivityRecord activity = createActivityRecord(mDisplayContent); - final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, activity, - "startingWin"); + final WindowState startingWin = newWindowBuilder("startingWin", + TYPE_APPLICATION_STARTING).setWindowToken(activity).build(); startingWin.setHasSurface(true); assertTrue(startingWin.canBeImeTarget()); @@ -532,8 +534,8 @@ public class DisplayContentTests extends WindowTestsBase { mWm.mPerDisplayFocusEnabled = perDisplayFocusEnabled; // Create a focusable window and check that focus is calculated correctly - final WindowState window1 = - createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "window1"); + final WindowState window1 = newWindowBuilder("window1", TYPE_BASE_APPLICATION).setDisplay( + mDisplayContent).build(); window1.mActivityRecord.mTargetSdk = targetSdk; updateFocusedWindow(); assertTrue(window1.isFocused()); @@ -549,7 +551,8 @@ public class DisplayContentTests extends WindowTestsBase { final ActivityRecord app2 = new ActivityBuilder(mAtm) .setTask(new TaskBuilder(mSupervisor).setDisplay(dc).build()) .setUseProcess(window1.getProcess()).setOnTop(true).build(); - final WindowState window2 = createWindow(null, TYPE_BASE_APPLICATION, app2, "window2"); + final WindowState window2 = newWindowBuilder("window2", + TYPE_BASE_APPLICATION).setWindowToken(app2).build(); window2.mActivityRecord.mTargetSdk = targetSdk; updateFocusedWindow(); assertTrue(window2.isFocused()); @@ -616,7 +619,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testDisplayHasContent() { - final WindowState window = createWindow(null, TYPE_APPLICATION_OVERLAY, "window"); + final WindowState window = newWindowBuilder("window", TYPE_APPLICATION_OVERLAY).build(); setDrawnState(WindowStateAnimator.COMMIT_DRAW_PENDING, window); assertFalse(mDisplayContent.getLastHasContent()); // The pending draw state should be committed and the has-content state is also updated. @@ -632,7 +635,8 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testImeIsAttachedToDisplayForLetterboxedApp() { final DisplayContent dc = mDisplayContent; - final WindowState ws = createWindow(null, TYPE_APPLICATION, dc, "app window"); + final WindowState ws = newWindowBuilder("app window", TYPE_APPLICATION).setDisplay( + dc).build(); dc.setImeLayeringTarget(ws); dc.setImeInputTarget(ws); @@ -655,7 +659,8 @@ public class DisplayContentTests extends WindowTestsBase { final WindowState[] windows = new WindowState[types.length]; for (int i = 0; i < types.length; i++) { final int type = types[i]; - windows[i] = createWindow(null /* parent */, type, displayContent, "window-" + type); + windows[i] = newWindowBuilder("window-" + type, type).setDisplay( + displayContent).build(); windows[i].setHasSurface(true); windows[i].mWinAnimator.mDrawState = WindowStateAnimator.DRAW_PENDING; } @@ -887,7 +892,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testLayoutSeq_assignedDuringLayout() { final DisplayContent dc = createNewDisplay(); - final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w"); + final WindowState win = newWindowBuilder("w", TYPE_BASE_APPLICATION).setDisplay(dc).build(); performLayout(dc); @@ -902,10 +907,12 @@ public class DisplayContentTests extends WindowTestsBase { // Create a window that requests landscape orientation. It will define device orientation // by default. - final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w"); + final WindowState window = newWindowBuilder("w", TYPE_BASE_APPLICATION).setDisplay( + dc).build(); window.mActivityRecord.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); - final WindowState keyguard = createWindow(null, TYPE_NOTIFICATION_SHADE , dc, "keyguard"); + final WindowState keyguard = newWindowBuilder("keyguard", + TYPE_NOTIFICATION_SHADE).setDisplay(dc).build(); keyguard.mHasSurface = true; keyguard.mAttrs.screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED; @@ -936,8 +943,8 @@ public class DisplayContentTests extends WindowTestsBase { // Create a window that requests a fixed orientation. It will define device orientation // by default. - final WindowState window = createWindow(null /* parent */, TYPE_APPLICATION_OVERLAY, dc, - "window"); + final WindowState window = newWindowBuilder("window", TYPE_APPLICATION_OVERLAY).setDisplay( + dc).build(); window.mHasSurface = true; window.mAttrs.screenOrientation = SCREEN_ORIENTATION_LANDSCAPE; @@ -1003,12 +1010,14 @@ public class DisplayContentTests extends WindowTestsBase { public void testInputMethodTargetUpdateWhenSwitchingOnDisplays() { final DisplayContent newDisplay = createNewDisplay(); - final WindowState appWin = createWindow(null, TYPE_APPLICATION, mDisplayContent, "appWin"); + final WindowState appWin = newWindowBuilder("appWin", TYPE_APPLICATION).setDisplay( + mDisplayContent).build(); final Task rootTask = mDisplayContent.getTopRootTask(); final ActivityRecord activity = rootTask.topRunningActivity(); doReturn(true).when(activity).shouldBeVisibleUnchecked(); - final WindowState appWin1 = createWindow(null, TYPE_APPLICATION, newDisplay, "appWin1"); + final WindowState appWin1 = newWindowBuilder("appWin1", TYPE_APPLICATION).setDisplay( + newDisplay).build(); final Task rootTask1 = newDisplay.getTopRootTask(); final ActivityRecord activity1 = rootTask1.topRunningActivity(); doReturn(true).when(activity1).shouldBeVisibleUnchecked(); @@ -1203,7 +1212,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testComputeImeParent_app() throws Exception { final DisplayContent dc = createNewDisplay(); - dc.setImeLayeringTarget(createWindow(null, TYPE_BASE_APPLICATION, "app")); + dc.setImeLayeringTarget(newWindowBuilder("app", TYPE_BASE_APPLICATION).build()); dc.setImeInputTarget(dc.getImeTarget(IME_TARGET_LAYERING).getWindow()); assertEquals(dc.getImeTarget( IME_TARGET_LAYERING).getWindow().mActivityRecord.getSurfaceControl(), @@ -1213,7 +1222,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testComputeImeParent_app_notFullscreen() throws Exception { final DisplayContent dc = createNewDisplay(); - dc.setImeLayeringTarget(createWindow(null, TYPE_STATUS_BAR, "app")); + dc.setImeLayeringTarget(newWindowBuilder("app", TYPE_STATUS_BAR).build()); dc.getImeTarget(IME_TARGET_LAYERING).getWindow().setWindowingMode( WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW); dc.setImeInputTarget(dc.getImeTarget(IME_TARGET_LAYERING).getWindow()); @@ -1235,7 +1244,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testComputeImeParent_noApp() throws Exception { final DisplayContent dc = createNewDisplay(); - dc.setImeLayeringTarget(createWindow(null, TYPE_STATUS_BAR, "statusBar")); + dc.setImeLayeringTarget(newWindowBuilder("statusBar", TYPE_STATUS_BAR).build()); dc.setImeInputTarget(dc.getImeTarget(IME_TARGET_LAYERING).getWindow()); assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent().getSurfaceControl()); @@ -1244,8 +1253,8 @@ public class DisplayContentTests extends WindowTestsBase { @SetupWindows(addWindows = W_ACTIVITY) @Test public void testComputeImeParent_inputTargetNotUpdate() throws Exception { - WindowState app1 = createWindow(null, TYPE_BASE_APPLICATION, "app1"); - WindowState app2 = createWindow(null, TYPE_BASE_APPLICATION, "app2"); + WindowState app1 = newWindowBuilder("app1", TYPE_BASE_APPLICATION).build(); + WindowState app2 = newWindowBuilder("app2", TYPE_BASE_APPLICATION).build(); doReturn(true).when(mDisplayContent).shouldImeAttachedToApp(); mDisplayContent.setImeLayeringTarget(app1); mDisplayContent.setImeInputTarget(app1); @@ -1260,10 +1269,10 @@ public class DisplayContentTests extends WindowTestsBase { @SetupWindows(addWindows = W_ACTIVITY) @Test public void testComputeImeParent_updateParentWhenTargetNotUseIme() throws Exception { - WindowState overlay = createWindow(null, TYPE_APPLICATION_OVERLAY, "overlay"); + WindowState overlay = newWindowBuilder("overlay", TYPE_APPLICATION_OVERLAY).build(); overlay.setBounds(100, 100, 200, 200); overlay.mAttrs.flags = FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM; - WindowState app = createWindow(null, TYPE_BASE_APPLICATION, "app"); + WindowState app = newWindowBuilder("app", TYPE_BASE_APPLICATION).build(); mDisplayContent.setImeLayeringTarget(overlay); mDisplayContent.setImeInputTarget(app); assertFalse(mDisplayContent.shouldImeAttachedToApp()); @@ -1274,8 +1283,8 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testComputeImeParent_remoteControlTarget() throws Exception { final DisplayContent dc = mDisplayContent; - WindowState app1 = createWindow(null, TYPE_BASE_APPLICATION, "app1"); - WindowState app2 = createWindow(null, TYPE_BASE_APPLICATION, "app2"); + WindowState app1 = newWindowBuilder("app1", TYPE_BASE_APPLICATION).build(); + WindowState app2 = newWindowBuilder("app2", TYPE_BASE_APPLICATION).build(); dc.setImeLayeringTarget(app1); dc.setImeInputTarget(app2); @@ -1301,7 +1310,7 @@ public class DisplayContentTests extends WindowTestsBase { public void testInputMethodInputTarget_isClearedWhenWindowStateIsRemoved() throws Exception { final DisplayContent dc = createNewDisplay(); - WindowState app = createWindow(null, TYPE_BASE_APPLICATION, dc, "app"); + WindowState app = newWindowBuilder("app", TYPE_BASE_APPLICATION).setDisplay(dc).build(); dc.setImeInputTarget(app); assertEquals(app, dc.computeImeControlTarget()); @@ -1316,7 +1325,7 @@ public class DisplayContentTests extends WindowTestsBase { public void testComputeImeControlTarget() throws Exception { final DisplayContent dc = createNewDisplay(); dc.setRemoteInsetsController(createDisplayWindowInsetsController()); - dc.mCurrentFocus = createWindow(null, TYPE_BASE_APPLICATION, "app"); + dc.mCurrentFocus = newWindowBuilder("app", TYPE_BASE_APPLICATION).build(); // Expect returning null IME control target when the focus window has not yet been the // IME input target (e.g. IME is restarting) in fullscreen windowing mode. @@ -1332,7 +1341,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testComputeImeControlTarget_splitscreen() throws Exception { final DisplayContent dc = createNewDisplay(); - dc.setImeInputTarget(createWindow(null, TYPE_BASE_APPLICATION, "app")); + dc.setImeInputTarget(newWindowBuilder("app", TYPE_BASE_APPLICATION).build()); dc.getImeInputTarget().getWindowState().setWindowingMode( WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW); dc.setImeLayeringTarget(dc.getImeInputTarget().getWindowState()); @@ -1346,7 +1355,7 @@ public class DisplayContentTests extends WindowTestsBase { public void testImeSecureFlagGetUpdatedAfterImeInputTarget() { // Verify IME window can get up-to-date secure flag update when the IME input target // set before setCanScreenshot called. - final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build(); SurfaceControl.Transaction t = mDisplayContent.mInputMethodWindow.getPendingTransaction(); spyOn(t); mDisplayContent.setImeInputTarget(app); @@ -1391,7 +1400,8 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testUpdateSystemGestureExclusion() throws Exception { final DisplayContent dc = createNewDisplay(); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win"); + final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setDisplay( + dc).build(); win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR; win.setSystemGestureExclusion(Collections.singletonList(new Rect(10, 20, 30, 40))); @@ -1423,11 +1433,12 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testCalculateSystemGestureExclusion() throws Exception { final DisplayContent dc = createNewDisplay(); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win"); + final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setDisplay( + dc).build(); win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR; win.setSystemGestureExclusion(Collections.singletonList(new Rect(10, 20, 30, 40))); - final WindowState win2 = createWindow(null, TYPE_APPLICATION, dc, "win2"); + final WindowState win2 = newWindowBuilder("win2", TYPE_APPLICATION).setDisplay(dc).build(); win2.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR; win2.setSystemGestureExclusion(Collections.singletonList(new Rect(20, 30, 40, 50))); @@ -1451,11 +1462,12 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testCalculateSystemGestureExclusion_modal() throws Exception { final DisplayContent dc = createNewDisplay(); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "base"); + final WindowState win = newWindowBuilder("base", TYPE_BASE_APPLICATION).setDisplay( + dc).build(); win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR; win.setSystemGestureExclusion(Collections.singletonList(new Rect(0, 0, 1000, 1000))); - final WindowState win2 = createWindow(null, TYPE_APPLICATION, dc, "modal"); + final WindowState win2 = newWindowBuilder("modal", TYPE_APPLICATION).setDisplay(dc).build(); win2.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR; win2.getAttrs().privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION; win2.getAttrs().width = 10; @@ -1476,7 +1488,8 @@ public class DisplayContentTests extends WindowTestsBase { mWm.mConstants.mSystemGestureExcludedByPreQStickyImmersive = true; final DisplayContent dc = createNewDisplay(); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win"); + final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setDisplay( + dc).build(); win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR; win.getAttrs().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; win.getAttrs().privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION; @@ -1500,7 +1513,8 @@ public class DisplayContentTests extends WindowTestsBase { mWm.mConstants.mSystemGestureExcludedByPreQStickyImmersive = true; final DisplayContent dc = createNewDisplay(); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win"); + final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setDisplay( + dc).build(); win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR; win.getAttrs().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; win.getAttrs().privateFlags |= PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION; @@ -1559,9 +1573,9 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testHybridRotationAnimation() { final DisplayContent displayContent = mDefaultDisplay; - final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar"); - final WindowState navBar = createWindow(null, TYPE_NAVIGATION_BAR, "navBar"); - final WindowState app = createWindow(null, TYPE_BASE_APPLICATION, "app"); + final WindowState statusBar = newWindowBuilder("statusBar", TYPE_STATUS_BAR).build(); + final WindowState navBar = newWindowBuilder("navBar", TYPE_NAVIGATION_BAR).build(); + final WindowState app = newWindowBuilder("app", TYPE_BASE_APPLICATION).build(); final WindowState[] windows = { statusBar, navBar, app }; makeWindowVisible(windows); final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy(); @@ -1863,6 +1877,11 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals("Display must be portrait after closing the translucent activity", Configuration.ORIENTATION_PORTRAIT, mDisplayContent.getConfiguration().orientation); + + mDisplayContent.setFixedRotationLaunchingAppUnchecked(nonTopVisible); + mDisplayContent.onTransitionFinished(); + assertFalse("Complete fixed rotation if not in a transition", + mDisplayContent.hasTopFixedRotationLaunchingApp()); } @Test @@ -2183,7 +2202,8 @@ public class DisplayContentTests extends WindowTestsBase { Task rootTask = createTask(display); Task task = createTaskInRootTask(rootTask, 0 /* userId */); WindowState activityWindow = createAppWindow(task, TYPE_APPLICATION, "App Window"); - WindowState behindWindow = createWindow(null, TYPE_SCREENSHOT, display, "Screenshot"); + WindowState behindWindow = newWindowBuilder("Screenshot", TYPE_SCREENSHOT).setDisplay( + display).build(); WindowState result = display.findScrollCaptureTargetWindow(behindWindow, ActivityTaskManager.INVALID_TASK_ID); @@ -2196,7 +2216,7 @@ public class DisplayContentTests extends WindowTestsBase { Task rootTask = createTask(display); Task task = createTaskInRootTask(rootTask, 0 /* userId */); WindowState activityWindow = createAppWindow(task, TYPE_APPLICATION, "App Window"); - WindowState invisible = createWindow(null, TYPE_APPLICATION, "invisible"); + WindowState invisible = newWindowBuilder("invisible", TYPE_APPLICATION).build(); invisible.mViewVisibility = View.INVISIBLE; // make canReceiveKeys return false WindowState result = display.findScrollCaptureTargetWindow(null, @@ -2209,7 +2229,7 @@ public class DisplayContentTests extends WindowTestsBase { DisplayContent display = createNewDisplay(); Task rootTask = createTask(display); Task task = createTaskInRootTask(rootTask, 0 /* userId */); - WindowState secureWindow = createWindow(null, TYPE_APPLICATION, "Secure Window"); + WindowState secureWindow = newWindowBuilder("Secure Window", TYPE_APPLICATION).build(); secureWindow.mAttrs.flags |= FLAG_SECURE; WindowState result = display.findScrollCaptureTargetWindow(null, @@ -2222,7 +2242,7 @@ public class DisplayContentTests extends WindowTestsBase { DisplayContent display = createNewDisplay(); Task rootTask = createTask(display); Task task = createTaskInRootTask(rootTask, 0 /* userId */); - WindowState secureWindow = createWindow(null, TYPE_APPLICATION, "Secure Window"); + WindowState secureWindow = newWindowBuilder("Secure window", TYPE_APPLICATION).build(); secureWindow.mAttrs.flags |= FLAG_SECURE; WindowState result = display.findScrollCaptureTargetWindow(null, task.mTaskId); @@ -2235,7 +2255,8 @@ public class DisplayContentTests extends WindowTestsBase { Task rootTask = createTask(display); Task task = createTaskInRootTask(rootTask, 0 /* userId */); WindowState window = createAppWindow(task, TYPE_APPLICATION, "App Window"); - WindowState behindWindow = createWindow(null, TYPE_SCREENSHOT, display, "Screenshot"); + WindowState behindWindow = newWindowBuilder("Screenshot", TYPE_SCREENSHOT).setDisplay( + display).build(); WindowState result = display.findScrollCaptureTargetWindow(null, task.mTaskId); assertEquals(window, result); @@ -2248,7 +2269,8 @@ public class DisplayContentTests extends WindowTestsBase { Task task = createTaskInRootTask(rootTask, 0 /* userId */); WindowState window = createAppWindow(task, TYPE_APPLICATION, "App Window"); window.mViewVisibility = View.INVISIBLE; // make canReceiveKeys return false - WindowState behindWindow = createWindow(null, TYPE_SCREENSHOT, display, "Screenshot"); + WindowState behindWindow = newWindowBuilder("Screenshot", TYPE_SCREENSHOT).setDisplay( + display).build(); WindowState result = display.findScrollCaptureTargetWindow(null, task.mTaskId); assertEquals(window, result); @@ -2317,9 +2339,10 @@ public class DisplayContentTests extends WindowTestsBase { @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD }) @Test public void testComputeImeTarget_shouldNotCheckOutdatedImeTargetLayerWhenRemoved() { - final WindowState child1 = createWindow(mAppWindow, FIRST_SUB_WINDOW, "child1"); - final WindowState nextImeTargetApp = createWindow(null /* parent */, - TYPE_BASE_APPLICATION, "nextImeTargetApp"); + final WindowState child1 = newWindowBuilder("child1", FIRST_SUB_WINDOW).setParent( + mAppWindow).build(); + final WindowState nextImeTargetApp = newWindowBuilder("nextImeTargetApp", + TYPE_BASE_APPLICATION).build(); spyOn(child1); doReturn(false).when(mDisplayContent).shouldImeAttachedToApp(); mDisplayContent.setImeLayeringTarget(child1); @@ -2353,7 +2376,8 @@ public class DisplayContentTests extends WindowTestsBase { // Preparation: Simulate snapshot Task. ActivityRecord act1 = createActivityRecord(mDisplayContent); - final WindowState appWin1 = createWindow(null, TYPE_BASE_APPLICATION, act1, "appWin1"); + final WindowState appWin1 = newWindowBuilder("appWin1", + TYPE_BASE_APPLICATION).setWindowToken(act1).build(); spyOn(appWin1); spyOn(appWin1.mWinAnimator); appWin1.setHasSurface(true); @@ -2372,7 +2396,8 @@ public class DisplayContentTests extends WindowTestsBase { // Test step 2: Simulate launching appWin2 and appWin1 is in app transition. ActivityRecord act2 = createActivityRecord(mDisplayContent); - final WindowState appWin2 = createWindow(null, TYPE_BASE_APPLICATION, act2, "appWin2"); + final WindowState appWin2 = newWindowBuilder("appWin2", + TYPE_BASE_APPLICATION).setWindowToken(act2).build(); appWin2.setHasSurface(true); assertTrue(appWin2.canBeImeTarget()); doReturn(true).when(appWin1).inTransitionSelfOrParent(); @@ -2394,7 +2419,8 @@ public class DisplayContentTests extends WindowTestsBase { final Task rootTask = createTask(mDisplayContent); final Task task = createTaskInRootTask(rootTask, 0 /* userId */); final ActivityRecord activity = createActivityRecord(mDisplayContent, task); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win"); + final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken( + activity).build(); task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE); doReturn(true).when(task).okToAnimate(); ArrayList<WindowContainer> sources = new ArrayList<>(); @@ -2420,7 +2446,8 @@ public class DisplayContentTests extends WindowTestsBase { final Task rootTask = createTask(mDisplayContent); final Task task = createTaskInRootTask(rootTask, 0 /* userId */); final ActivityRecord activity = createActivityRecord(mDisplayContent, task); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win"); + final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken( + activity).build(); mDisplayContent.setImeLayeringTarget(win); mDisplayContent.setImeInputTarget(win); @@ -2446,7 +2473,8 @@ public class DisplayContentTests extends WindowTestsBase { final Task rootTask = createTask(mDisplayContent); final Task task = createTaskInRootTask(rootTask, 0 /* userId */); final ActivityRecord activity = createActivityRecord(mDisplayContent, task); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win"); + final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken( + activity).build(); win.onSurfaceShownChanged(true); makeWindowVisible(win, mDisplayContent.mInputMethodWindow); task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE); @@ -2471,7 +2499,8 @@ public class DisplayContentTests extends WindowTestsBase { final Task rootTask = createTask(mDisplayContent); final Task task = createTaskInRootTask(rootTask, 0 /* userId */); final ActivityRecord activity = createActivityRecord(mDisplayContent, task); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win"); + final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken( + activity).build(); makeWindowVisible(mDisplayContent.mInputMethodWindow); mDisplayContent.setImeLayeringTarget(win); @@ -2687,7 +2716,8 @@ public class DisplayContentTests extends WindowTestsBase { public void testKeyguardGoingAwayWhileAodShown() { mDisplayContent.getDisplayPolicy().setAwake(true); - final WindowState appWin = createWindow(null, TYPE_APPLICATION, mDisplayContent, "appWin"); + final WindowState appWin = newWindowBuilder("appWin", TYPE_APPLICATION).setDisplay( + mDisplayContent).build(); final ActivityRecord activity = appWin.mActivityRecord; mAtm.mKeyguardController.setKeyguardShown(appWin.getDisplayId(), true /* keyguardShowing */, @@ -2713,15 +2743,15 @@ public class DisplayContentTests extends WindowTestsBase { @SetupWindows(addWindows = W_INPUT_METHOD) @Test public void testImeChildWindowFocusWhenImeLayeringTargetChanges() { - final WindowState imeChildWindow = - createWindow(mImeWindow, TYPE_APPLICATION_ATTACHED_DIALOG, "imeChildWindow"); + final WindowState imeChildWindow = newWindowBuilder("imeChildWindow", + TYPE_APPLICATION_ATTACHED_DIALOG).setParent(mImeWindow).build(); makeWindowVisibleAndDrawn(imeChildWindow, mImeWindow); assertTrue(imeChildWindow.canReceiveKeys()); mDisplayContent.setInputMethodWindowLocked(mImeWindow); // Verify imeChildWindow can be focused window if the next IME target requests IME visible. - final WindowState imeAppTarget = - createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget"); + final WindowState imeAppTarget = newWindowBuilder("imeAppTarget", + TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); mDisplayContent.setImeLayeringTarget(imeAppTarget); spyOn(imeAppTarget); doReturn(true).when(imeAppTarget).isRequestedVisible(ime()); @@ -2729,8 +2759,8 @@ public class DisplayContentTests extends WindowTestsBase { // Verify imeChildWindow doesn't be focused window if the next IME target does not // request IME visible. - final WindowState nextImeAppTarget = - createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "nextImeAppTarget"); + final WindowState nextImeAppTarget = newWindowBuilder("nextImeAppTarget", + TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); mDisplayContent.setImeLayeringTarget(nextImeAppTarget); assertNotEquals(imeChildWindow, mDisplayContent.findFocusedWindow()); } @@ -2738,22 +2768,22 @@ public class DisplayContentTests extends WindowTestsBase { @SetupWindows(addWindows = W_INPUT_METHOD) @Test public void testImeMenuDialogFocusWhenImeLayeringTargetChanges() { - final WindowState imeMenuDialog = - createWindow(null, TYPE_INPUT_METHOD_DIALOG, "imeMenuDialog"); + final WindowState imeMenuDialog = newWindowBuilder("imeMenuDialog", + TYPE_INPUT_METHOD_DIALOG).build(); makeWindowVisibleAndDrawn(imeMenuDialog, mImeWindow); assertTrue(imeMenuDialog.canReceiveKeys()); mDisplayContent.setInputMethodWindowLocked(mImeWindow); // Verify imeMenuDialog can be focused window if the next IME target requests IME visible. - final WindowState imeAppTarget = - createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget"); + final WindowState imeAppTarget = newWindowBuilder("imeAppTarget", + TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); mDisplayContent.setImeLayeringTarget(imeAppTarget); imeAppTarget.setRequestedVisibleTypes(ime()); assertEquals(imeMenuDialog, mDisplayContent.findFocusedWindow()); // Verify imeMenuDialog doesn't be focused window if the next IME target is closing. - final WindowState nextImeAppTarget = - createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "nextImeAppTarget"); + final WindowState nextImeAppTarget = newWindowBuilder("nextImeAppTarget", + TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); makeWindowVisibleAndDrawn(nextImeAppTarget); // Even if the app still requests IME, the ime dialog should not gain focus if the target // app is invisible. @@ -2765,10 +2795,12 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testKeepClearAreasMultipleWindows() { - final WindowState w1 = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent, "w1"); + final WindowState w1 = newWindowBuilder("w1", TYPE_NAVIGATION_BAR).setDisplay( + mDisplayContent).build(); final Rect rect1 = new Rect(0, 0, 10, 10); w1.setKeepClearAreas(Arrays.asList(rect1), Collections.emptyList()); - final WindowState w2 = createWindow(null, TYPE_NOTIFICATION_SHADE, mDisplayContent, "w2"); + final WindowState w2 = newWindowBuilder("w2", TYPE_NOTIFICATION_SHADE).setDisplay( + mDisplayContent).build(); final Rect rect2 = new Rect(10, 10, 20, 20); w2.setKeepClearAreas(Arrays.asList(rect2), Collections.emptyList()); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java index 1015651438c3..ceb06497adbc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java @@ -76,7 +76,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { @Before public void setUp() throws Exception { - mWindow = spy(createWindow(null, TYPE_APPLICATION, "window")); + mWindow = spy(newWindowBuilder("window", TYPE_APPLICATION).build()); spyOn(mStatusBarWindow); spyOn(mNavBarWindow); @@ -147,7 +147,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { public void addingWindow_withInsetsTypes() { mDisplayPolicy.removeWindowLw(mStatusBarWindow); // Removes the existing one. - final WindowState win = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "statusBar"); + final WindowState win = newWindowBuilder("statusBar", TYPE_STATUS_BAR_SUB_PANEL).build(); final Binder owner = new Binder(); win.mAttrs.providedInsets = new InsetsFrameProvider[] { new InsetsFrameProvider(owner, 0, WindowInsets.Type.statusBars()), diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java index 27d46fc4e39e..ea925c019b77 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -76,7 +76,7 @@ import org.junit.runner.RunWith; public class DisplayPolicyTests extends WindowTestsBase { private WindowState createOpaqueFullscreen(boolean hasLightNavBar) { - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "opaqueFullscreen"); + final WindowState win = newWindowBuilder("opaqueFullscreen", TYPE_BASE_APPLICATION).build(); final WindowManager.LayoutParams attrs = win.mAttrs; attrs.width = MATCH_PARENT; attrs.height = MATCH_PARENT; @@ -99,7 +99,7 @@ public class DisplayPolicyTests extends WindowTestsBase { } private WindowState createDimmingDialogWindow(boolean canBeImTarget) { - final WindowState win = spy(createWindow(null, TYPE_APPLICATION, "dimmingDialog")); + final WindowState win = spy(newWindowBuilder("dimmingDialog", TYPE_APPLICATION).build()); final WindowManager.LayoutParams attrs = win.mAttrs; attrs.width = WRAP_CONTENT; attrs.height = WRAP_CONTENT; @@ -111,7 +111,7 @@ public class DisplayPolicyTests extends WindowTestsBase { private WindowState createInputMethodWindow(boolean visible, boolean drawNavBar, boolean hasLightNavBar) { - final WindowState win = createWindow(null, TYPE_INPUT_METHOD, "inputMethod"); + final WindowState win = newWindowBuilder("inputMethod", TYPE_INPUT_METHOD).build(); final WindowManager.LayoutParams attrs = win.mAttrs; attrs.width = MATCH_PARENT; attrs.height = MATCH_PARENT; @@ -301,7 +301,7 @@ public class DisplayPolicyTests extends WindowTestsBase { } private WindowState createApplicationWindow() { - final WindowState win = createWindow(null, TYPE_APPLICATION, "Application"); + final WindowState win = newWindowBuilder("Application", TYPE_APPLICATION).build(); final WindowManager.LayoutParams attrs = win.mAttrs; attrs.width = MATCH_PARENT; attrs.height = MATCH_PARENT; @@ -312,7 +312,7 @@ public class DisplayPolicyTests extends WindowTestsBase { } private WindowState createBaseApplicationWindow() { - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "Application"); + final WindowState win = newWindowBuilder("Application", TYPE_BASE_APPLICATION).build(); final WindowManager.LayoutParams attrs = win.mAttrs; attrs.width = MATCH_PARENT; attrs.height = MATCH_PARENT; @@ -450,7 +450,7 @@ public class DisplayPolicyTests extends WindowTestsBase { displayPolicy.getDecorInsetsInfo(Surface.ROTATION_90, di.logicalHeight, di.logicalWidth); // Add a window that provides the same insets in current rotation. But it specifies // different insets in other rotations. - final WindowState bar2 = createWindow(null, navbar.mAttrs.type, "bar2"); + final WindowState bar2 = newWindowBuilder("bar2", navbar.mAttrs.type).build(); bar2.mAttrs.providedInsets = new InsetsFrameProvider[] { new InsetsFrameProvider(bar2, 0, WindowInsets.Type.navigationBars()) .setInsetsSize(Insets.of(0, 0, 0, NAV_BAR_HEIGHT)) diff --git a/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java b/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java index 34f0c191ecf5..fe9f63615757 100644 --- a/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java +++ b/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java @@ -76,6 +76,7 @@ public class IntegrationTests { private ActivityTestRule<EmptyActivity> mEmptyActivityRule = new ActivityTestRule<>(EmptyActivity.class, false , true); + @Before public void setUp() { mInstrumentation = InstrumentationRegistry.getInstrumentation(); @@ -163,7 +164,7 @@ public class IntegrationTests { // of that state. for (int i = 0; i < uiStates.size(); i++) { StateTracker.StateData stateData = uiStates.get(i); - if (stateData.mWidgetCategory.equals(AppJankStats.ANIMATION)) { + if (stateData.mWidgetCategory.equals(AppJankStats.WIDGET_CATEGORY_ANIMATION)) { assertNotEquals(Long.MAX_VALUE, stateData.mVsyncIdEnd); } } diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java b/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java index 30c568be7716..c90595782cd1 100644 --- a/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java +++ b/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java @@ -215,7 +215,8 @@ public class JankDataProcessorTest { assertEquals(jankStats.getJankyFrameCount() * 2, pendingStat.getJankyFrames()); assertEquals(jankStats.getTotalFrameCount() * 2, pendingStat.getTotalFrames()); - int[] originalHistogramBuckets = jankStats.getFrameOverrunHistogram().getBucketCounters(); + int[] originalHistogramBuckets = + jankStats.getRelativeFrameTimeHistogram().getBucketCounters(); int[] frameOverrunBuckets = pendingStat.getFrameOverrunBuckets(); for (int i = 0; i < frameOverrunBuckets.length; i++) { diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java b/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java index 0b4d97ed20d6..df92898d76b1 100644 --- a/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java +++ b/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java @@ -17,7 +17,7 @@ package android.app.jank.tests; import android.app.jank.AppJankStats; -import android.app.jank.FrameOverrunHistogram; +import android.app.jank.RelativeFrameTimeHistogram; public class JankUtils { private static final int APP_ID = 25; @@ -29,8 +29,8 @@ public class JankUtils { AppJankStats jankStats = new AppJankStats( /*App Uid*/APP_ID, /*Widget Id*/"test widget id", - /*Widget Category*/AppJankStats.SCROLL, - /*Widget State*/AppJankStats.SCROLLING, + /*Widget Category*/AppJankStats.WIDGET_CATEGORY_SCROLL, + /*Widget State*/AppJankStats.WIDGET_STATE_SCROLLING, /*Total Frames*/100, /*Janky Frames*/25, getOverrunHistogram() @@ -41,12 +41,12 @@ public class JankUtils { /** * Returns a mock histogram to be used with an AppJankStats object. */ - public static FrameOverrunHistogram getOverrunHistogram() { - FrameOverrunHistogram overrunHistogram = new FrameOverrunHistogram(); - overrunHistogram.addFrameOverrunMillis(-2); - overrunHistogram.addFrameOverrunMillis(1); - overrunHistogram.addFrameOverrunMillis(5); - overrunHistogram.addFrameOverrunMillis(25); + public static RelativeFrameTimeHistogram getOverrunHistogram() { + RelativeFrameTimeHistogram overrunHistogram = new RelativeFrameTimeHistogram(); + overrunHistogram.addRelativeFrameTimeMillis(-2); + overrunHistogram.addRelativeFrameTimeMillis(1); + overrunHistogram.addRelativeFrameTimeMillis(5); + overrunHistogram.addRelativeFrameTimeMillis(25); return overrunHistogram; } } diff --git a/tests/AppJankTest/src/android/app/jank/tests/TestWidget.java b/tests/AppJankTest/src/android/app/jank/tests/TestWidget.java index 5fff46038ead..71796d64ddee 100644 --- a/tests/AppJankTest/src/android/app/jank/tests/TestWidget.java +++ b/tests/AppJankTest/src/android/app/jank/tests/TestWidget.java @@ -45,8 +45,8 @@ public class TestWidget extends View { */ public void simulateAnimationStarting() { if (jankTrackerCreated()) { - mJankTracker.addUiState(AppJankStats.ANIMATION, - Integer.toString(this.getId()), AppJankStats.ANIMATING); + mJankTracker.addUiState(AppJankStats.WIDGET_CATEGORY_ANIMATION, + Integer.toString(this.getId()), AppJankStats.WIDGET_STATE_ANIMATING); } } @@ -55,8 +55,8 @@ public class TestWidget extends View { */ public void simulateAnimationEnding() { if (jankTrackerCreated()) { - mJankTracker.removeUiState(AppJankStats.ANIMATION, - Integer.toString(this.getId()), AppJankStats.ANIMATING); + mJankTracker.removeUiState(AppJankStats.WIDGET_CATEGORY_ANIMATION, + Integer.toString(this.getId()), AppJankStats.WIDGET_STATE_ANIMATING); } } diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java index 700856c50bae..14c8de8db5fc 100644 --- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java +++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java @@ -819,7 +819,7 @@ public class GraphicsActivity extends Activity { private List<Float> getExpectedFrameRateForCompatibility(int compatibility) { assumeTrue("**** testSurfaceControlFrameRateCompatibility SKIPPED for compatibility " + compatibility, - compatibility == Surface.FRAME_RATE_COMPATIBILITY_GTE); + compatibility == Surface.FRAME_RATE_COMPATIBILITY_AT_LEAST); Display display = getDisplay(); List<Float> expectedFrameRates = getRefreshRates(display.getMode(), display) diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java index 4d4827676c74..f1d4dc6b8faf 100644 --- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java +++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java @@ -85,7 +85,8 @@ public class SurfaceControlTest { @Test public void testSurfaceControlFrameRateCompatibilityGte() throws InterruptedException { GraphicsActivity activity = mActivityRule.getActivity(); - activity.testSurfaceControlFrameRateCompatibility(Surface.FRAME_RATE_COMPATIBILITY_GTE); + activity.testSurfaceControlFrameRateCompatibility( + Surface.FRAME_RATE_COMPATIBILITY_AT_LEAST); } @Test diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt index 2e7b20763b9e..2db8b1e18ec8 100644 --- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt @@ -29,6 +29,7 @@ import android.tools.helpers.SYSTEMUI_PACKAGE import android.tools.traces.parsers.WindowManagerStateHelper import android.tools.traces.wm.WindowingMode import android.view.KeyEvent.KEYCODE_LEFT_BRACKET +import android.view.KeyEvent.KEYCODE_MINUS import android.view.KeyEvent.KEYCODE_RIGHT_BRACKET import android.view.KeyEvent.META_META_ON import android.view.WindowInsets @@ -160,10 +161,21 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) : ?: error("Unable to find resource $MINIMIZE_BUTTON_VIEW\n") } - fun minimizeDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice, isPip: Boolean = false) { - val caption = getCaptionForTheApp(wmHelper, device) - val minimizeButton = getMinimizeButtonForTheApp(caption) - minimizeButton.click() + fun minimizeDesktopApp( + wmHelper: WindowManagerStateHelper, + device: UiDevice, + isPip: Boolean = false, + usingKeyboard: Boolean = false, + ) { + if (usingKeyboard) { + val keyEventHelper = KeyEventHelper(getInstrumentation()) + keyEventHelper.press(KEYCODE_MINUS, META_META_ON) + } else { + val caption = getCaptionForTheApp(wmHelper, device) + val minimizeButton = getMinimizeButtonForTheApp(caption) + minimizeButton.click() + } + wmHelper .StateSyncBuilder() .withAppTransitionIdle() @@ -226,8 +238,7 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) : toLeft: Boolean, ) { val bracketKey = if (toLeft) KEYCODE_LEFT_BRACKET else KEYCODE_RIGHT_BRACKET - keyEventHelper.actionDown(bracketKey, META_META_ON) - keyEventHelper.actionUp(bracketKey, META_META_ON) + keyEventHelper.press(bracketKey, META_META_ON) waitAndVerifySnapResize(wmHelper, context, toLeft) } diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/KeyEventHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/KeyEventHelper.kt index ebd8cc3ce1b4..55ed09154aee 100644 --- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/KeyEventHelper.kt +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/KeyEventHelper.kt @@ -29,6 +29,10 @@ import android.view.KeyEvent class KeyEventHelper( private val instr: Instrumentation, ) { + fun press(keyCode: Int, metaState: Int = 0) { + actionDown(keyCode, metaState) + actionUp(keyCode, metaState) + } fun actionDown(keyCode: Int, metaState: Int = 0, time: Long = SystemClock.uptimeMillis()) { injectKeyEvent(ACTION_DOWN, keyCode, metaState, downTime = time, eventTime = time) diff --git a/tests/Input/src/com/android/test/input/KeyCharacterMapTest.kt b/tests/Input/src/com/android/test/input/KeyCharacterMapTest.kt index 281837920548..860d9f680c4c 100644 --- a/tests/Input/src/com/android/test/input/KeyCharacterMapTest.kt +++ b/tests/Input/src/com/android/test/input/KeyCharacterMapTest.kt @@ -16,10 +16,17 @@ package com.android.test.input +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule + import android.view.KeyCharacterMap import android.view.KeyEvent +import com.android.hardware.input.Flags + import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Rule import org.junit.Test /** @@ -30,26 +37,38 @@ import org.junit.Test * */ class KeyCharacterMapTest { + @get:Rule + val setFlagsRule = SetFlagsRule() + @Test + @EnableFlags(Flags.FLAG_REMOVE_FALLBACK_MODIFIERS) fun testGetFallback() { // Based off of VIRTUAL kcm fallbacks. val keyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD) // One modifier fallback. - assertEquals( - keyCharacterMap.getFallbackAction(KeyEvent.KEYCODE_SPACE, - KeyEvent.META_CTRL_ON).keyCode, - KeyEvent.KEYCODE_LANGUAGE_SWITCH) + val oneModifierFallback = keyCharacterMap.getFallbackAction(KeyEvent.KEYCODE_SPACE, + KeyEvent.META_CTRL_ON) + assertEquals(KeyEvent.KEYCODE_LANGUAGE_SWITCH, oneModifierFallback.keyCode) + assertEquals(0, oneModifierFallback.metaState) // Multiple modifier fallback. - assertEquals( - keyCharacterMap.getFallbackAction(KeyEvent.KEYCODE_DEL, - KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON).keyCode, - KeyEvent.KEYCODE_BACK) + val twoModifierFallback = keyCharacterMap.getFallbackAction(KeyEvent.KEYCODE_DEL, + KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON) + assertEquals(KeyEvent.KEYCODE_BACK, twoModifierFallback.keyCode) + assertEquals(0, twoModifierFallback.metaState) // No default button, fallback only. - assertEquals( - keyCharacterMap.getFallbackAction(KeyEvent.KEYCODE_BUTTON_A, 0).keyCode, - KeyEvent.KEYCODE_DPAD_CENTER) + val keyOnlyFallback = + keyCharacterMap.getFallbackAction(KeyEvent.KEYCODE_BUTTON_A, 0) + assertEquals(KeyEvent.KEYCODE_DPAD_CENTER, keyOnlyFallback.keyCode) + assertEquals(0, keyOnlyFallback.metaState) + + // A key event that is not an exact match for a fallback. Expect a null return. + // E.g. Ctrl + Space -> LanguageSwitch + // Ctrl + Alt + Space -> Ctrl + Alt + Space (No fallback). + val noMatchFallback = keyCharacterMap.getFallbackAction(KeyEvent.KEYCODE_SPACE, + KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON) + assertNull(noMatchFallback) } } diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp index 44aa4028c916..370c0048d9a9 100644 --- a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp +++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp @@ -38,6 +38,9 @@ android_test { ], test_suites: [ "general-tests", + // This is an equivalent of general-tests for automotive. + // It helps manage the build time on automotive branches. + "automotive-general-tests", ], sdk_version: "test_current", diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp index 5e0f87f0dcaf..60c4bf5c4131 100644 --- a/tools/aapt/Package.cpp +++ b/tools/aapt/Package.cpp @@ -292,13 +292,12 @@ bool processFile(Bundle* bundle, ZipFile* zip, } if (!hasData) { const String8& srcName = file->getSourceFile(); - time_t fileModWhen; - fileModWhen = getFileModDate(srcName.c_str()); - if (fileModWhen == (time_t) -1) { // file existence tested earlier, - return false; // not expecting an error here + auto fileModWhen = getFileModDate(srcName.c_str()); + if (fileModWhen == kInvalidModDate) { // file existence tested earlier, + return false; // not expecting an error here } - - if (fileModWhen > entry->getModWhen()) { + + if (toTimeT(fileModWhen) > entry->getModWhen()) { // mark as deleted so add() will succeed if (bundle->getVerbose()) { printf(" (removing old '%s')\n", storageName.c_str()); diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index 661df4d0fe33..e24fe07f959b 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -683,8 +683,6 @@ class ChunkPrinter { item->PrettyPrint(printer_); printer_->Print(")"); } - - printer_->Print("\n"); } void PrintQualifiers(uint32_t qualifiers) const { @@ -763,11 +761,13 @@ class ChunkPrinter { bool PrintTableType(const ResTable_type* chunk) { printer_->Print(StringPrintf(" id: 0x%02x", android::util::DeviceToHost32(chunk->id))); - printer_->Print(StringPrintf( - " name: %s", - android::util::GetString(type_pool_, android::util::DeviceToHost32(chunk->id) - 1) - .c_str())); + const auto name = + android::util::GetString(type_pool_, android::util::DeviceToHost32(chunk->id) - 1); + printer_->Print(StringPrintf(" name: %s", name.c_str())); printer_->Print(StringPrintf(" flags: 0x%02x", android::util::DeviceToHost32(chunk->flags))); + printer_->Print(android::util::DeviceToHost32(chunk->flags) & ResTable_type::FLAG_SPARSE + ? " (SPARSE)" + : " (DENSE)"); printer_->Print( StringPrintf(" entryCount: %u", android::util::DeviceToHost32(chunk->entryCount))); printer_->Print( @@ -777,8 +777,7 @@ class ChunkPrinter { config.copyFromDtoH(chunk->config); printer_->Print(StringPrintf(" config: %s\n", config.to_string().c_str())); - const ResourceType* type = ParseResourceType( - android::util::GetString(type_pool_, android::util::DeviceToHost32(chunk->id) - 1)); + const ResourceType* type = ParseResourceType(name); printer_->Indent(); @@ -817,11 +816,8 @@ class ChunkPrinter { for (size_t i = 0; i < map_entry_count; i++) { PrintResValue(&(maps[i].value), config, type); - printer_->Print(StringPrintf( - " name: %s name-id:%d\n", - android::util::GetString(key_pool_, android::util::DeviceToHost32(maps[i].name.ident)) - .c_str(), - android::util::DeviceToHost32(maps[i].name.ident))); + printer_->Print(StringPrintf(" name-id: 0x%08x\n", + android::util::DeviceToHost32(maps[i].name.ident))); } } else { printer_->Print("\n"); @@ -829,6 +825,8 @@ class ChunkPrinter { // Print the value of the entry Res_value value = entry->value(); PrintResValue(&value, config, type); + + printer_->Print("\n"); } printer_->Undent(); |